├── .gitignore ├── LICENSE.TXT ├── Makefile ├── README.md ├── bin ├── htdocs │ └── test.html └── server.config └── src ├── ByteBuffer.cpp ├── ByteBuffer.h ├── Client.cpp ├── Client.h ├── HTTPMessage.cpp ├── HTTPMessage.h ├── HTTPRequest.cpp ├── HTTPRequest.h ├── HTTPResponse.cpp ├── HTTPResponse.h ├── HTTPServer.cpp ├── HTTPServer.h ├── MimeTypes.inc ├── Resource.cpp ├── Resource.h ├── ResourceHost.cpp ├── ResourceHost.h ├── SendQueueItem.h ├── convert_mimetypes.py └── main.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | bin/httpserver 2 | build/ 3 | *DS_Store* 4 | 5 | .idea/* 6 | bin/htdocs/* 7 | 8 | *.o 9 | -------------------------------------------------------------------------------- /LICENSE.TXT: -------------------------------------------------------------------------------- 1 | httpserver 2 | Copyright 2011-2025 Ramsey Kant 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for httpserver 2 | # (C) Ramsey Kant 2011-2025 3 | 4 | DEST = httpserver 5 | CLANG_FORMAT = clang-format 6 | LDFLAGS ?= 7 | CXXFLAGS ?= 8 | CXX = clang++ 9 | 10 | CXXFLAGS += -Wall -Wextra -Wno-sign-compare -Wno-missing-field-initializers \ 11 | -Wformat -Wformat=2 -Wimplicit-fallthrough \ 12 | -march=x86-64-v2 -fPIE \ 13 | -fexceptions \ 14 | -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer \ 15 | -fno-delete-null-pointer-checks -fno-strict-aliasing \ 16 | -pedantic -std=c++23 17 | 18 | LDFLAGS += -fuse-ld=lld 19 | 20 | ifeq ($(DEBUG),1) 21 | CXXFLAGS += -O1 -g -DDEBUG=1 \ 22 | -fasynchronous-unwind-tables \ 23 | -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG \ 24 | -fstack-protector-all 25 | else 26 | CXXFLAGS += -O2 -DNDEBUG -flto=auto 27 | LDFLAGS += -flto=auto 28 | endif 29 | 30 | SOURCES = $(sort $(wildcard src/*.cpp)) 31 | OBJECTS = $(SOURCES:.cpp=.o) 32 | CLEANFILES = $(OBJECTS) bin/$(DEST) 33 | 34 | all: make-src 35 | 36 | make-src: $(DEST) 37 | 38 | $(DEST): $(OBJECTS) 39 | $(CXX) $(LDFLAGS) $(CXXFLAGS) $(OBJECTS) -o bin/$@ 40 | 41 | clean: 42 | rm -f $(CLEANFILES) 43 | 44 | debug: 45 | DEBUG=1 $(MAKE) all 46 | 47 | asan: 48 | CXXFLAGS=-fsanitize=address DEBUG=1 $(MAKE) all 49 | 50 | .PHONY: all make-src clean debug asan 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTTP Server 2 | 3 | Ramsey Kant 4 | https://github.com/RamseyK/httpserver 5 | 6 | A high performance, single threaded, HTTP/1.1 server written in C++ to serve as a kqueue socket management and HTTP/1.1 protocol learning tool on BSD systems 7 | 8 | ## Features 9 | * Clean, documented code 10 | * Efficient socket management with kqueue 11 | * Easy to understand HTTP protocol parser (from my [ByteBuffer](https://github.com/RamseyK/ByteBufferCpp) project) 12 | * Tested on FreeBSD and macOS 13 | 14 | ## Compiling 15 | * Only BSD based systems are supported. Linux _may_ work when [libkqueue is compiled from Github sources](https://github.com/mheily/libkqueue) and linked, but this is unsupported. 16 | * On FreeBSD, compile with gmake 17 | 18 | ## Usage 19 | 20 | ``` 21 | 22 | $ cat server.config 23 | vhost=10.0.10.86,acme.local 24 | port=8080 25 | diskpath=./htdocs 26 | 27 | $ ./httpserver 28 | Primary port: 8080, disk path: ./htdocs 29 | vhost: 10.0.10.86 30 | vhost: acme.local 31 | Server ready. Listening on port 8080... 32 | ``` 33 | 34 | ## License 35 | See LICENSE.TXT 36 | -------------------------------------------------------------------------------- /bin/htdocs/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Title 5 | 6 | 7 | 8 | 9 | Hello World!
10 | 11 | 12 | -------------------------------------------------------------------------------- /bin/server.config: -------------------------------------------------------------------------------- 1 | # Hosts to respond to (localhost/127.0.0.1 implied) 2 | vhost=10.0.10.86,acme.local 3 | port=8080 4 | diskpath=./htdocs 5 | 6 | # Optional - uid/gid to "drop" to with setuid/setgid after bind() so the program doesn't have to remain as root 7 | # Default 0 because dropping to root makes no sense 8 | drop_uid=0 9 | drop_gid=0 10 | -------------------------------------------------------------------------------- /src/ByteBuffer.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | ByteBuffer 3 | ByteBuffer.cpp 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | Modified 2015 by Ashley Davis (SgtCoDFish) 19 | */ 20 | 21 | #include "ByteBuffer.h" 22 | 23 | #ifdef BB_UTILITY 24 | #include 25 | #include 26 | #include 27 | #endif 28 | 29 | #ifdef BB_USE_NS 30 | namespace bb { 31 | #endif 32 | 33 | /** 34 | * ByteBuffer constructor 35 | * Reserves specified size in internal vector 36 | * 37 | * @param size Size (in bytes) of space to preallocate internally. Default is set in BB_DEFAULT_SIZE 38 | */ 39 | ByteBuffer::ByteBuffer(uint32_t size) { 40 | buf.reserve(size); 41 | clear(); 42 | } 43 | 44 | /** 45 | * ByteBuffer constructor 46 | * Consume an entire byte array of length len in the ByteBuffer 47 | * 48 | * @param arr byte array of data (should be of length len) 49 | * @param size Size of space to allocate 50 | */ 51 | ByteBuffer::ByteBuffer(const uint8_t* arr, uint32_t size) { 52 | // If the provided array is NULL, allocate a blank buffer of the provided size 53 | if (arr == nullptr) { 54 | buf.reserve(size); 55 | clear(); 56 | } else { // Consume the provided array 57 | buf.reserve(size); 58 | clear(); 59 | putBytes(arr, size); 60 | } 61 | } 62 | 63 | /** 64 | * Bytes Remaining 65 | * Returns the number of bytes from the current read position till the end of the buffer 66 | * 67 | * @return Number of bytes from rpos to the end (size()) 68 | */ 69 | uint32_t ByteBuffer::bytesRemaining() const { 70 | return size() - rpos; 71 | } 72 | 73 | /** 74 | * Clear 75 | * Clears out all data from the internal vector (original preallocated size remains), resets the positions to 0 76 | */ 77 | void ByteBuffer::clear() { 78 | rpos = 0; 79 | wpos = 0; 80 | buf.clear(); 81 | } 82 | 83 | /** 84 | * Clone 85 | * Allocate an exact copy of the ByteBuffer on the heap and return a pointer 86 | * 87 | * @return A pointer to the newly cloned ByteBuffer. NULL if no more memory available 88 | */ 89 | std::unique_ptr ByteBuffer::clone() { 90 | auto ret = std::make_unique(buf.size()); 91 | 92 | // Copy data 93 | for (uint32_t i = 0; i < buf.size(); i++) { 94 | ret->put(get(i)); 95 | } 96 | 97 | // Reset positions 98 | ret->setReadPos(0); 99 | ret->setWritePos(0); 100 | 101 | return ret; 102 | } 103 | 104 | /** 105 | * Equals, test for data equivilancy 106 | * Compare this ByteBuffer to another by looking at each byte in the internal buffers and making sure they are the same 107 | * 108 | * @param other A pointer to a ByteBuffer to compare to this one 109 | * @return True if the internal buffers match. False if otherwise 110 | */ 111 | bool ByteBuffer::equals(const ByteBuffer* other) const { 112 | // If sizes aren't equal, they can't be equal 113 | if (size() != other->size()) 114 | return false; 115 | 116 | // Compare byte by byte 117 | uint32_t len = size(); 118 | for (uint32_t i = 0; i < len; i++) { 119 | if (get(i) != other->get(i)) 120 | return false; 121 | } 122 | 123 | return true; 124 | } 125 | 126 | /** 127 | * Resize 128 | * Reallocates memory for the internal buffer of size newSize. Read and write positions will also be reset 129 | * 130 | * @param newSize The amount of memory to allocate 131 | */ 132 | void ByteBuffer::resize(uint32_t newSize) { 133 | buf.resize(newSize); 134 | rpos = 0; 135 | wpos = 0; 136 | } 137 | 138 | /** 139 | * Size 140 | * Returns the size of the internal buffer...not necessarily the length of bytes used as data! 141 | * 142 | * @return size of the internal buffer 143 | */ 144 | uint32_t ByteBuffer::size() const { 145 | return buf.size(); 146 | } 147 | 148 | // Replacement 149 | 150 | /** 151 | * Replace 152 | * Replace occurrence of a particular byte, key, with the byte rep 153 | * 154 | * @param key Byte to find for replacement 155 | * @param rep Byte to replace the found key with 156 | * @param start Index to start from. By default, start is 0 157 | * @param firstOccurrenceOnly If true, only replace the first occurrence of the key. If false, replace all occurrences. False by default 158 | */ 159 | void ByteBuffer::replace(uint8_t key, uint8_t rep, uint32_t start, bool firstOccurrenceOnly) { 160 | uint32_t len = buf.size(); 161 | for (uint32_t i = start; i < len; i++) { 162 | uint8_t data = read(i); 163 | // Wasn't actually found, bounds of buffer were exceeded 164 | if ((key != 0) && (data == 0)) 165 | break; 166 | 167 | // Key was found in array, perform replacement 168 | if (data == key) { 169 | buf[i] = rep; 170 | if (firstOccurrenceOnly) 171 | return; 172 | } 173 | } 174 | } 175 | 176 | // Read Functions 177 | 178 | uint8_t ByteBuffer::peek() const { 179 | return read(rpos); 180 | } 181 | 182 | uint8_t ByteBuffer::get() { 183 | return read(); 184 | } 185 | 186 | uint8_t ByteBuffer::get(uint32_t index) const { 187 | return read(index); 188 | } 189 | 190 | void ByteBuffer::getBytes(uint8_t* const out_buf, uint32_t out_len) { 191 | for (uint32_t i = 0; i < out_len; i++) { 192 | out_buf[i] = read(); 193 | } 194 | } 195 | 196 | char ByteBuffer::getChar() { 197 | return read(); 198 | } 199 | 200 | char ByteBuffer::getChar(uint32_t index) const { 201 | return read(index); 202 | } 203 | 204 | double ByteBuffer::getDouble() { 205 | return read(); 206 | } 207 | 208 | double ByteBuffer::getDouble(uint32_t index) const { 209 | return read(index); 210 | } 211 | 212 | float ByteBuffer::getFloat() { 213 | return read(); 214 | } 215 | 216 | float ByteBuffer::getFloat(uint32_t index) const { 217 | return read(index); 218 | } 219 | 220 | uint32_t ByteBuffer::getInt() { 221 | return read(); 222 | } 223 | 224 | uint32_t ByteBuffer::getInt(uint32_t index) const { 225 | return read(index); 226 | } 227 | 228 | uint64_t ByteBuffer::getLong() { 229 | return read(); 230 | } 231 | 232 | uint64_t ByteBuffer::getLong(uint32_t index) const { 233 | return read(index); 234 | } 235 | 236 | uint16_t ByteBuffer::getShort() { 237 | return read(); 238 | } 239 | 240 | uint16_t ByteBuffer::getShort(uint32_t index) const { 241 | return read(index); 242 | } 243 | 244 | 245 | // Write Functions 246 | 247 | void ByteBuffer::put(const ByteBuffer* src) { 248 | uint32_t len = src->size(); 249 | for (uint32_t i = 0; i < len; i++) 250 | append(src->get(i)); 251 | } 252 | 253 | void ByteBuffer::put(uint8_t b) { 254 | append(b); 255 | } 256 | 257 | void ByteBuffer::put(uint8_t b, uint32_t index) { 258 | insert(b, index); 259 | } 260 | 261 | void ByteBuffer::putBytes(const uint8_t* const b, uint32_t len) { 262 | // Insert the data one byte at a time into the internal buffer at position i+starting index 263 | for (uint32_t i = 0; i < len; i++) 264 | append(b[i]); 265 | } 266 | 267 | void ByteBuffer::putBytes(const uint8_t* const b, uint32_t len, uint32_t index) { 268 | wpos = index; 269 | 270 | // Insert the data one byte at a time into the internal buffer at position i+starting index 271 | for (uint32_t i = 0; i < len; i++) 272 | append(b[i]); 273 | } 274 | 275 | void ByteBuffer::putChar(char value) { 276 | append(value); 277 | } 278 | 279 | void ByteBuffer::putChar(char value, uint32_t index) { 280 | insert(value, index); 281 | } 282 | 283 | void ByteBuffer::putDouble(double value) { 284 | append(value); 285 | } 286 | 287 | void ByteBuffer::putDouble(double value, uint32_t index) { 288 | insert(value, index); 289 | } 290 | void ByteBuffer::putFloat(float value) { 291 | append(value); 292 | } 293 | 294 | void ByteBuffer::putFloat(float value, uint32_t index) { 295 | insert(value, index); 296 | } 297 | 298 | void ByteBuffer::putInt(uint32_t value) { 299 | append(value); 300 | } 301 | 302 | void ByteBuffer::putInt(uint32_t value, uint32_t index) { 303 | insert(value, index); 304 | } 305 | 306 | void ByteBuffer::putLong(uint64_t value) { 307 | append(value); 308 | } 309 | 310 | void ByteBuffer::putLong(uint64_t value, uint32_t index) { 311 | insert(value, index); 312 | } 313 | 314 | void ByteBuffer::putShort(uint16_t value) { 315 | append(value); 316 | } 317 | 318 | void ByteBuffer::putShort(uint16_t value, uint32_t index) { 319 | insert(value, index); 320 | } 321 | 322 | // Utility Functions 323 | #ifdef BB_UTILITY 324 | void ByteBuffer::setName(std::string_view n) { 325 | name = n; 326 | } 327 | 328 | std::string ByteBuffer::getName() const { 329 | return name; 330 | } 331 | 332 | void ByteBuffer::printInfo() const { 333 | uint32_t length = buf.size(); 334 | std::cout << "ByteBuffer " << name << " Length: " << length << ". Info Print" << std::endl; 335 | } 336 | 337 | void ByteBuffer::printAH() const { 338 | uint32_t length = buf.size(); 339 | std::cout << "ByteBuffer " << name << " Length: " << length << ". ASCII & Hex Print" << std::endl; 340 | for (uint32_t i = 0; i < length; i++) { 341 | std::cout << "0x" << std::setw(2) << std::setfill('0') << std::hex << int(buf[i]) << " "; 342 | } 343 | std::printf("\n"); 344 | for (uint32_t i = 0; i < length; i++) { 345 | std::cout << (char)buf[i] << " "; 346 | } 347 | std::cout << std::endl; 348 | } 349 | 350 | void ByteBuffer::printAscii() const { 351 | uint32_t length = buf.size(); 352 | std::cout << "ByteBuffer " << name << " Length: " << length << ". ASCII Print" << std::endl; 353 | for (uint32_t i = 0; i < length; i++) { 354 | std::cout << (char)buf[i] << " "; 355 | } 356 | std::cout << std::endl; 357 | } 358 | 359 | void ByteBuffer::printHex() const { 360 | uint32_t length = buf.size(); 361 | std::cout << "ByteBuffer " << name << " Length: " << length << ". Hex Print" << std::endl; 362 | for (uint32_t i = 0; i < length; i++) { 363 | std::cout << "0x" << std::setw(2) << std::setfill('0') << std::hex << int(buf[i]) << " "; 364 | } 365 | std::cout << std::endl; 366 | } 367 | 368 | void ByteBuffer::printPosition() const { 369 | uint32_t length = buf.size(); 370 | std::cout << "ByteBuffer " << name << " Length: " << length << " Read Pos: " << rpos << ". Write Pos: " 371 | << wpos << std::endl; 372 | } 373 | 374 | #endif // BB_UTILITY 375 | 376 | #ifdef BB_USE_NS 377 | } 378 | #endif 379 | -------------------------------------------------------------------------------- /src/ByteBuffer.h: -------------------------------------------------------------------------------- 1 | /** 2 | ByteBuffer 3 | ByteBuffer.h 4 | Copyright 2011-2015 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | 18 | Modified 2015 by Ashley Davis (SgtCoDFish) 19 | */ 20 | 21 | #ifndef _BYTEBUFFER_H_ 22 | #define _BYTEBUFFER_H_ 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #ifdef BB_UTILITY 29 | #include 30 | #endif 31 | 32 | // Default number of bytes to allocate in the backing buffer if no size is provided 33 | constexpr uint32_t BB_DEFAULT_SIZE = 4096; 34 | 35 | #ifdef BB_USE_NS 36 | namespace bb { 37 | #endif 38 | 39 | class ByteBuffer { 40 | public: 41 | explicit ByteBuffer(uint32_t size = BB_DEFAULT_SIZE); 42 | explicit ByteBuffer(const uint8_t* arr, uint32_t size); 43 | virtual ~ByteBuffer() = default; 44 | 45 | uint32_t bytesRemaining() const; // Number of bytes from the current read position till the end of the buffer 46 | void clear(); // Clear our the vector and reset read and write positions 47 | std::unique_ptr clone(); // Return a new instance of a ByteBuffer with the exact same contents and the same state (rpos, wpos) 48 | bool equals(const ByteBuffer* other) const; // Compare if the contents are equivalent 49 | void resize(uint32_t newSize); 50 | uint32_t size() const; // Size of internal vector 51 | 52 | // Basic Searching (Linear) 53 | template int32_t find(T key, uint32_t start=0) { 54 | int32_t ret = -1; 55 | uint32_t len = buf.size(); 56 | for (uint32_t i = start; i < len; i++) { 57 | T data = read(i); 58 | // Wasn't actually found, bounds of buffer were exceeded 59 | if ((key != 0) && (data == 0)) 60 | break; 61 | 62 | // Key was found in array 63 | if (data == key) { 64 | ret = i; 65 | break; 66 | } 67 | } 68 | return ret; 69 | } 70 | 71 | // Replacement 72 | void replace(uint8_t key, uint8_t rep, uint32_t start = 0, bool firstOccurrenceOnly=false); 73 | 74 | // Read 75 | 76 | uint8_t peek() const; // Relative peek. Reads and returns the next byte in the buffer from the current position but does not increment the read position 77 | uint8_t get(); // Relative get method. Reads the byte at the buffers current position then increments the position 78 | uint8_t get(uint32_t index) const; // Absolute get method. Read byte at index 79 | void getBytes(uint8_t* const out_buf, uint32_t out_len); // Absolute read into array buf of length len 80 | char getChar(); // Relative 81 | char getChar(uint32_t index) const; // Absolute 82 | double getDouble(); 83 | double getDouble(uint32_t index) const; 84 | float getFloat(); 85 | float getFloat(uint32_t index) const; 86 | uint32_t getInt(); 87 | uint32_t getInt(uint32_t index) const; 88 | uint64_t getLong(); 89 | uint64_t getLong(uint32_t index) const; 90 | uint16_t getShort(); 91 | uint16_t getShort(uint32_t index) const; 92 | 93 | // Write 94 | 95 | void put(const ByteBuffer* src); // Relative write of the entire contents of another ByteBuffer (src) 96 | void put(uint8_t b); // Relative write 97 | void put(uint8_t b, uint32_t index); // Absolute write at index 98 | void putBytes(const uint8_t* const b, uint32_t len); // Relative write 99 | void putBytes(const uint8_t* const b, uint32_t len, uint32_t index); // Absolute write starting at index 100 | void putChar(char value); // Relative 101 | void putChar(char value, uint32_t index); // Absolute 102 | void putDouble(double value); 103 | void putDouble(double value, uint32_t index); 104 | void putFloat(float value); 105 | void putFloat(float value, uint32_t index); 106 | void putInt(uint32_t value); 107 | void putInt(uint32_t value, uint32_t index); 108 | void putLong(uint64_t value); 109 | void putLong(uint64_t value, uint32_t index); 110 | void putShort(uint16_t value); 111 | void putShort(uint16_t value, uint32_t index); 112 | 113 | // Buffer Position Accessors & Mutators 114 | 115 | void setReadPos(uint32_t r) { 116 | rpos = r; 117 | } 118 | 119 | uint32_t getReadPos() const { 120 | return rpos; 121 | } 122 | 123 | void setWritePos(uint32_t w) { 124 | wpos = w; 125 | } 126 | 127 | uint32_t getWritePos() const { 128 | return wpos; 129 | } 130 | 131 | // Utility Functions 132 | #ifdef BB_UTILITY 133 | void setName(std::string_view n); 134 | std::string getName() const; 135 | void printInfo() const; 136 | void printAH() const; 137 | void printAscii() const; 138 | void printHex() const; 139 | void printPosition() const; 140 | #endif 141 | 142 | private: 143 | uint32_t rpos = 0; 144 | uint32_t wpos = 0; 145 | std::vector buf; 146 | 147 | #ifdef BB_UTILITY 148 | std::string name = ""; 149 | #endif 150 | 151 | template T read() { 152 | T data = read(rpos); 153 | rpos += sizeof(T); 154 | return data; 155 | } 156 | 157 | template T read(uint32_t index) const { 158 | if (index + sizeof(T) <= buf.size()) 159 | return *((T* const)&buf[index]); 160 | return 0; 161 | } 162 | 163 | template void append(T data) { 164 | uint32_t s = sizeof(data); 165 | 166 | if (size() < (wpos + s)) 167 | buf.resize(wpos + s); 168 | memcpy(&buf[wpos], (uint8_t*)&data, s); 169 | 170 | wpos += s; 171 | } 172 | 173 | template void insert(T data, uint32_t index) { 174 | if ((index + sizeof(data)) > size()) { 175 | buf.resize(size() + (index + sizeof(data))); 176 | } 177 | 178 | memcpy(&buf[index], (uint8_t*)&data, sizeof(data)); 179 | wpos = index + sizeof(data); 180 | } 181 | }; 182 | 183 | #ifdef BB_USE_NS 184 | } 185 | #endif 186 | 187 | #endif 188 | -------------------------------------------------------------------------------- /src/Client.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | httpserver 3 | Client.cpp 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #include "Client.h" 20 | 21 | Client::Client(int fd, sockaddr_in addr) : socketDesc(fd), clientAddr(addr) { 22 | } 23 | 24 | Client::~Client() { 25 | clearSendQueue(); 26 | } 27 | 28 | /** 29 | * Add to Send Queue 30 | * Adds a SendQueueItem object to the end of this client's send queue 31 | */ 32 | void Client::addToSendQueue(SendQueueItem* item) { 33 | sendQueue.push(item); 34 | } 35 | 36 | /** 37 | * sendQueueSize 38 | * Returns the current number of SendQueueItem's in the send queue 39 | * 40 | * @return Integer representing number of items in this clients send queue 41 | */ 42 | uint32_t Client::sendQueueSize() const { 43 | return sendQueue.size(); 44 | } 45 | 46 | /** 47 | * Next from Send Queue 48 | * Returns the current SendQueueItem object to be sent to the client 49 | * 50 | * @return SendQueueItem object containing the data to send and current offset 51 | */ 52 | SendQueueItem* Client::nextInSendQueue() { 53 | if (sendQueue.empty()) 54 | return nullptr; 55 | 56 | return sendQueue.front(); 57 | } 58 | 59 | /** 60 | * Dequeue from Send Queue 61 | * Deletes and dequeues first item in the queue 62 | */ 63 | void Client::dequeueFromSendQueue() { 64 | SendQueueItem* item = nextInSendQueue(); 65 | if (item != nullptr) { 66 | sendQueue.pop(); 67 | delete item; 68 | } 69 | } 70 | 71 | /** 72 | * Clear Send Queue 73 | * Clears out the send queue for the client, deleting all internal SendQueueItem objects 74 | */ 75 | void Client::clearSendQueue() { 76 | while (!sendQueue.empty()) { 77 | delete sendQueue.front(); 78 | sendQueue.pop(); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Client.h: -------------------------------------------------------------------------------- 1 | /** 2 | httpserver 3 | Client.h 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #ifndef _CLIENT_H_ 20 | #define _CLIENT_H_ 21 | 22 | #include "SendQueueItem.h" 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | class Client { 29 | int32_t socketDesc; // Socket Descriptor 30 | sockaddr_in clientAddr; 31 | 32 | std::queue sendQueue; 33 | 34 | public: 35 | Client(int fd, sockaddr_in addr); 36 | ~Client(); 37 | Client& operator=(Client const&) = delete; // Copy assignment 38 | Client(Client &&) = delete; // Move 39 | Client& operator=(Client &&) = delete; // Move assignment 40 | 41 | sockaddr_in getClientAddr() const { 42 | return clientAddr; 43 | } 44 | 45 | int32_t getSocket() const { 46 | return socketDesc; 47 | } 48 | 49 | char* getClientIP() { 50 | return inet_ntoa(clientAddr.sin_addr); 51 | } 52 | 53 | void addToSendQueue(SendQueueItem* item); 54 | uint32_t sendQueueSize() const; 55 | SendQueueItem* nextInSendQueue(); 56 | void dequeueFromSendQueue(); 57 | void clearSendQueue(); 58 | }; 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /src/HTTPMessage.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | ByteBuffer 3 | HTTPMessage.cpp 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #include "HTTPMessage.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | HTTPMessage::HTTPMessage() : ByteBuffer(4096) { 27 | } 28 | 29 | HTTPMessage::HTTPMessage(std::string const& sData) : ByteBuffer(sData.size() + 1) { 30 | putBytes((const uint8_t* const)sData.c_str(), sData.size() + 1); 31 | } 32 | 33 | HTTPMessage::HTTPMessage(const uint8_t* pData, uint32_t len) : ByteBuffer(pData, len) { 34 | } 35 | 36 | /** 37 | * Put Line 38 | * Append a line (string) to the backing ByteBuffer at the current position 39 | * 40 | * @param str String to put into the ByteBuffer 41 | * @param crlf_end If true (default), end the line with a \r\n 42 | */ 43 | void HTTPMessage::putLine(std::string str, bool crlf_end) { 44 | // Terminate with crlf if flag set 45 | if (crlf_end) 46 | str += "\r\n"; 47 | 48 | // Put the entire contents of str into the buffer 49 | putBytes((const uint8_t* const)str.c_str(), str.size()); 50 | } 51 | 52 | /** 53 | * Put Headers 54 | * Write all headers currently in the 'headers' map to the ByteBuffer. 55 | * 'Header: value' 56 | */ 57 | void HTTPMessage::putHeaders() { 58 | for (auto const &[key, value] : headers) { 59 | putLine(std::format("{}: {}", key, value), true); 60 | } 61 | 62 | // End with a blank line 63 | putLine(); 64 | } 65 | 66 | /** 67 | * Get Line 68 | * Retrive the entire contents of a line: string from current position until CR or LF, whichever comes first, then increment the read position 69 | * until it's past the last CR or LF in the line 70 | * 71 | * @return Contents of the line in a string (without CR or LF) 72 | */ 73 | std::string HTTPMessage::getLine() { 74 | std::string ret = ""; 75 | int32_t startPos = getReadPos(); 76 | bool newLineReached = false; 77 | char c = 0; 78 | 79 | // Append characters to the return std::string until we hit the end of the buffer, a CR (13) or LF (10) 80 | for (uint32_t i = startPos; i < size(); i++) { 81 | // If the next byte is a \r or \n, we've reached the end of the line and should break out of the loop 82 | c = peek(); 83 | if ((c == 13) || (c == 10)) { 84 | newLineReached = true; 85 | break; 86 | } 87 | 88 | // Otherwise, append the next character to the std::string 89 | ret += getChar(); 90 | } 91 | 92 | // If a line termination was never reached, discard the result and conclude there are no more lines to parse 93 | if (!newLineReached) { 94 | setReadPos(startPos); // Reset the position to before the last line read that we are now discarding 95 | ret = ""; 96 | return ret; 97 | } 98 | 99 | // Increment the read position until the end of a CR or LF chain, so the read position will then point to the next line 100 | // Also, only read a maximum of 2 characters so as to not skip a blank line that is only \r\n 101 | uint32_t k = 0; 102 | for (uint32_t i = getReadPos(); i < size(); i++) { 103 | if (k++ >= 2) 104 | break; 105 | c = getChar(); 106 | if ((c != 13) && (c != 10)) { 107 | // Set the Read position back one because the retrived character wasn't a LF or CR 108 | setReadPos(getReadPos() - 1); 109 | break; 110 | } 111 | } 112 | 113 | return ret; 114 | } 115 | 116 | /** 117 | * getStrElement 118 | * Get a token from the current buffer, stopping at the delimiter. Returns the token as a string 119 | * 120 | * @param delim The delimiter to stop at when retriving the element. By default, it's a space 121 | * @return Token found in the buffer. Empty if delimiter wasn't reached 122 | */ 123 | std::string HTTPMessage::getStrElement(char delim) { 124 | int32_t startPos = getReadPos(); 125 | if (startPos < 0) 126 | return ""; 127 | 128 | int32_t endPos = find(delim, startPos); 129 | if (endPos < 0) 130 | return ""; 131 | 132 | if (startPos > endPos) 133 | return ""; 134 | 135 | // Calculate the size based on the found ending position 136 | uint32_t size = (endPos + 1) - startPos; 137 | if (size <= 0) 138 | return ""; 139 | 140 | // Grab the std::string from the ByteBuffer up to the delimiter 141 | auto str = std::make_unique(size); 142 | getBytes((uint8_t*)str.get(), size); 143 | str[size - 1] = 0x00; // NULL termination 144 | std::string ret = str.get(); 145 | 146 | // Increment the read position PAST the delimiter 147 | setReadPos(endPos + 1); 148 | 149 | return ret; 150 | } 151 | 152 | /** 153 | * Parse Headers 154 | * When an HTTP message (request & response) has reached the point where headers are present, this method 155 | * should be called to parse and populate the internal map of headers. 156 | * Parse headers will move the read position past the blank line that signals the end of the headers 157 | */ 158 | void HTTPMessage::parseHeaders() { 159 | std::string hline = ""; 160 | std::string app = ""; 161 | 162 | // Get the first header 163 | hline = getLine(); 164 | 165 | // Keep pulling headers until a blank line has been reached (signaling the end of headers) 166 | while (hline.size() > 0) { 167 | // Case where values are on multiple lines ending with a comma 168 | app = hline; 169 | while (app[app.size() - 1] == ',') { 170 | app = getLine(); 171 | hline += app; 172 | } 173 | 174 | addHeader(hline); 175 | hline = getLine(); 176 | } 177 | } 178 | 179 | /** 180 | * Parse Body 181 | * Parses everything after the headers section of an HTTP message. Handles chuncked responses/requests 182 | * 183 | * @return True if successful. False on error, parseErrorStr is set with a reason 184 | */ 185 | bool HTTPMessage::parseBody() { 186 | // Content-Length should exist (size of the Body data) if there is body data 187 | std::string hlenstr = ""; 188 | uint32_t contentLen = 0; 189 | hlenstr = getHeaderValue("Content-Length"); 190 | 191 | // No body data to read: 192 | if (hlenstr.empty()) 193 | return true; 194 | 195 | contentLen = atoi(hlenstr.c_str()); 196 | 197 | // contentLen should NOT exceed the remaining number of bytes in the buffer 198 | // Add 1 to bytesRemaining so it includes the byte at the current read position 199 | if (contentLen > bytesRemaining() + 1) { 200 | // If it exceeds, read only up to the number of bytes remaining 201 | // dataLen = bytesRemaining(); 202 | 203 | // If it exceeds, there's a potential security issue and we can't reliably parse 204 | parseErrorStr = std::format("Content-Length ({}) is greater than remaining bytes ({})", hlenstr, bytesRemaining()); 205 | return false; 206 | } else if (contentLen == 0) { 207 | // Nothing to read, which is fine 208 | return true; 209 | } else { 210 | // Otherwise, we can probably trust Content-Length is valid and read the specificed number of bytes 211 | dataLen = contentLen; 212 | } 213 | 214 | // Create a big enough buffer to store the data 215 | uint32_t dIdx = 0; 216 | uint32_t s = size(); 217 | if (s > dataLen) { 218 | parseErrorStr = std::format("ByteBuffer size of {} is greater than dataLen {}", s, dataLen); 219 | return false; 220 | } 221 | 222 | data = new uint8_t[dataLen]; 223 | 224 | // Grab all the bytes from the current position to the end 225 | for (uint32_t i = getReadPos(); i < s; i++) { 226 | data[dIdx] = get(i); 227 | dIdx++; 228 | } 229 | 230 | // We could handle chunked Request/Response parsing (with footers) here, but, we won't. 231 | 232 | return true; 233 | } 234 | 235 | /** 236 | * Add Header to the Map from string 237 | * Takes a formatted header string "Header: value", parse it, and put it into the std::map as a key,value pair. 238 | * 239 | * @param string containing formatted header: value 240 | */ 241 | void HTTPMessage::addHeader(std::string const& line) { 242 | size_t kpos = line.find(':'); 243 | if (kpos == std::string::npos) { 244 | std::cout << "Could not addHeader: " << line << std::endl; 245 | return; 246 | } 247 | // We're choosing to reject HTTP Header keys longer than 32 characters 248 | if (kpos > 32) 249 | return; 250 | 251 | std::string key = line.substr(0, kpos); 252 | if (key.empty()) 253 | return; 254 | 255 | int32_t value_len = line.size() - kpos - 1; 256 | if (value_len <= 0) 257 | return; 258 | 259 | // We're choosing to reject HTTP header values longer than 4kb 260 | if (value_len > 4096) 261 | return; 262 | 263 | std::string value = line.substr(kpos + 1, value_len); 264 | 265 | // Skip all leading spaces in the value 266 | int32_t i = 0; 267 | while (i < value.size() && value.at(i) == 0x20) { 268 | i++; 269 | } 270 | value = value.substr(i, value.size()); 271 | if (value.empty()) 272 | return; 273 | 274 | // Add header to the map 275 | addHeader(key, value); 276 | } 277 | 278 | /** 279 | * Add header key-value std::pair to the map 280 | * 281 | * @param key String representation of the Header Key 282 | * @param value String representation of the Header value 283 | */ 284 | void HTTPMessage::addHeader(std::string const& key, std::string const& value) { 285 | headers.try_emplace(key, value); 286 | } 287 | 288 | /** 289 | * Add header key-value std::pair to the map (Integer value) 290 | * Integer value is converted to a string 291 | * 292 | * @param key String representation of the Header Key 293 | * @param value Integer representation of the Header value 294 | */ 295 | void HTTPMessage::addHeader(std::string const& key, int32_t value) { 296 | headers.try_emplace(key, std::format("{}", value)); 297 | } 298 | 299 | /** 300 | * Get Header Value 301 | * Given a header name (key), return the value associated with it in the headers map 302 | * 303 | * @param key Key to identify the header 304 | */ 305 | std::string HTTPMessage::getHeaderValue(std::string const& key) const { 306 | 307 | char c = 0; 308 | std::string key_lower = ""; 309 | 310 | // Lookup in map 311 | auto it = headers.find(key); 312 | 313 | // Key wasn't found, try an all lowercase variant as some clients won't always use proper capitalization 314 | if (it == headers.end()) { 315 | 316 | for (uint32_t i = 0; i < key.length(); i++) { 317 | c = key.at(i); 318 | key_lower += tolower(c); 319 | } 320 | 321 | // Still not found, return empty string to indicate the Header value doesnt exist 322 | it = headers.find(key_lower); 323 | if (it == headers.end()) 324 | return ""; 325 | } 326 | 327 | // Otherwise, return the value 328 | return it->second; 329 | } 330 | 331 | /** 332 | * Get Header String 333 | * Get the full formatted header string "Header: value" from the headers map at position index 334 | * 335 | * @param index Position in the headers map to retrieve a formatted header string 336 | * @ret Formatted string with header name and value 337 | */ 338 | std::string HTTPMessage::getHeaderStr(int32_t index) const { 339 | int32_t i = 0; 340 | std::string ret = ""; 341 | for (auto const &[key, value] : headers) { 342 | if (i == index) { 343 | ret = std::format("{}: {}", key, value); 344 | break; 345 | } 346 | 347 | i++; 348 | } 349 | return ret; 350 | } 351 | 352 | /** 353 | * Get Number of Headers 354 | * Return the number of headers in the headers map 355 | * 356 | * @return size of the map 357 | */ 358 | uint32_t HTTPMessage::getNumHeaders() const { 359 | return headers.size(); 360 | } 361 | 362 | /** 363 | * Clear Headers 364 | * Removes all headers from the internal map 365 | */ 366 | void HTTPMessage::clearHeaders() { 367 | headers.clear(); 368 | } 369 | 370 | -------------------------------------------------------------------------------- /src/HTTPMessage.h: -------------------------------------------------------------------------------- 1 | /** 2 | ByteBuffer 3 | HTTPMessage.h 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #ifndef _HTTPMESSAGE_H_ 20 | #define _HTTPMESSAGE_H_ 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #include "ByteBuffer.h" 27 | 28 | // Constants 29 | constexpr std::string HTTP_VERSION_10 = "HTTP/1.0"; 30 | constexpr std::string HTTP_VERSION_11 = "HTTP/1.1"; 31 | constexpr std::string DEFAULT_HTTP_VERSION = HTTP_VERSION_11; 32 | constexpr uint32_t NUM_METHODS = 9; 33 | constexpr uint32_t INVALID_METHOD = 9999; 34 | static_assert(NUM_METHODS < INVALID_METHOD, "INVALID_METHOD must be greater than NUM_METHODS"); 35 | 36 | // HTTP Methods (Requests) 37 | 38 | enum Method { 39 | HEAD = 0, 40 | GET = 1, 41 | POST = 2, 42 | PUT = 3, 43 | DEL = 4, // DELETE is taken, use DEL instead 44 | TRACE = 5, 45 | OPTIONS = 6, 46 | CONNECT = 7, 47 | PATCH = 8 48 | }; 49 | 50 | const static char* const requestMethodStr[NUM_METHODS] = { 51 | "HEAD", // 0 52 | "GET", // 1 53 | "POST", // 2 54 | "PUT", // 3 55 | "DELETE", // 4 56 | "TRACE", // 5 57 | "OPTIONS", // 6 58 | "CONNECT", // 7 59 | "PATCH" // 8 60 | }; 61 | 62 | 63 | // HTTP Response Status codes 64 | enum Status { 65 | // 1xx Informational 66 | CONTINUE = 100, 67 | 68 | // 2xx Success 69 | OK = 200, 70 | 71 | // 3xx Redirection 72 | 73 | // 4xx Client Error 74 | BAD_REQUEST = 400, 75 | NOT_FOUND = 404, 76 | 77 | // 5xx Server Error 78 | SERVER_ERROR = 500, 79 | NOT_IMPLEMENTED = 501 80 | }; 81 | 82 | class HTTPMessage : public ByteBuffer { 83 | private: 84 | std::map> headers; 85 | 86 | public: 87 | std::string parseErrorStr = ""; 88 | 89 | std::string version = DEFAULT_HTTP_VERSION; // By default, all create() will indicate the version is whatever DEFAULT_HTTP_VERSION is 90 | 91 | // Message Body Data (Resource in the case of a response, extra parameters in the case of a request) 92 | uint8_t* data = nullptr; 93 | uint32_t dataLen = 0; 94 | 95 | public: 96 | HTTPMessage(); 97 | explicit HTTPMessage(std::string const& sData); 98 | explicit HTTPMessage(const uint8_t* pData, uint32_t len); 99 | ~HTTPMessage() override = default; 100 | 101 | virtual std::unique_ptr create() = 0; 102 | virtual bool parse() = 0; 103 | 104 | // Create helpers 105 | void putLine(std::string str = "", bool crlf_end = true); 106 | void putHeaders(); 107 | 108 | // Parse helpers 109 | std::string getLine(); 110 | std::string getStrElement(char delim = 0x20); // 0x20 = "space" 111 | void parseHeaders(); 112 | bool parseBody(); 113 | 114 | // Header Map manipulation 115 | void addHeader(std::string const& line); 116 | void addHeader(std::string const& key, std::string const& value); 117 | void addHeader(std::string const& key, int32_t value); 118 | std::string getHeaderValue(std::string const& key) const; 119 | std::string getHeaderStr(int32_t index) const; 120 | uint32_t getNumHeaders() const; 121 | void clearHeaders(); 122 | 123 | // Getters & Setters 124 | 125 | std::string getParseError() const { 126 | return parseErrorStr; 127 | } 128 | 129 | void setVersion(std::string_view v) { 130 | version = v; 131 | } 132 | 133 | std::string getVersion() const { 134 | return version; 135 | } 136 | 137 | void setData(uint8_t* d, uint32_t len) { 138 | data = d; 139 | dataLen = len; 140 | } 141 | 142 | uint8_t* getData() { 143 | return data; 144 | } 145 | 146 | uint32_t getDataLength() const { 147 | return dataLen; 148 | } 149 | }; 150 | 151 | #endif 152 | -------------------------------------------------------------------------------- /src/HTTPRequest.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | ByteBuffer 3 | HTTPRequest.cpp 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #include "HTTPMessage.h" 20 | #include "HTTPRequest.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | HTTPRequest::HTTPRequest() : HTTPMessage() { 27 | } 28 | 29 | HTTPRequest::HTTPRequest(std::string const& sData) : HTTPMessage(sData) { 30 | } 31 | 32 | HTTPRequest::HTTPRequest(const uint8_t* pData, uint32_t len) : HTTPMessage(pData, len) { 33 | } 34 | 35 | /** 36 | * Takes the method name and converts it to the corresponding method 37 | * id detailed in the Method enum 38 | * 39 | * @param name String representation of the Method 40 | * @return Corresponding Method ID, INVALID_METHOD if unable to find the method 41 | */ 42 | uint32_t HTTPRequest::methodStrToInt(std::string_view name) const { 43 | // Method name cannot must be between 1 and 10 characters. Anything outside those bounds shouldn't be compared at all 44 | if (name.empty() || (name.size() >= 10)) 45 | return INVALID_METHOD; 46 | 47 | // Loop through requestMethodStr array and attempt to match the 'name' with a known method in the array 48 | uint32_t ret = INVALID_METHOD; 49 | for (uint32_t i = 0; i < NUM_METHODS; i++) { 50 | if (name.compare(requestMethodStr[i]) == 0) { 51 | ret = i; 52 | break; 53 | } 54 | } 55 | return ret; 56 | } 57 | 58 | /** 59 | * Takes the method ID in the Method enum and returns the corresponding std::string representation 60 | * @param mid Method ID to lookup 61 | * @return The method name in the from of a std::string. Blank if unable to find the method 62 | */ 63 | std::string HTTPRequest::methodIntToStr(uint32_t mid) const { 64 | // ID is out of bounds of the possible requestMethodStr indexes 65 | if (mid >= NUM_METHODS) 66 | return ""; 67 | 68 | // Return the std::string matching the id 69 | return requestMethodStr[mid]; 70 | } 71 | 72 | /** 73 | * Create 74 | * Create and return a byte array of an HTTP request, built from the variables of this HTTPRequest 75 | * 76 | * @return Byte array of this HTTPRequest to be sent over the wire 77 | */ 78 | std::unique_ptr HTTPRequest::create() { 79 | // Clear the bytebuffer in the event this isn't the first call of create() 80 | clear(); 81 | 82 | // Insert the initial line: \r\n 83 | std::string mstr = ""; 84 | mstr = methodIntToStr(method); 85 | if (mstr.empty()) { 86 | std::cout << "Could not create HTTPRequest, unknown method id: " << method << std::endl; 87 | return nullptr; 88 | } 89 | putLine(std::format("{} {} {}", mstr, requestUri, version)); 90 | 91 | // Put all headers 92 | putHeaders(); 93 | 94 | // If theres body data, add it now 95 | if ((data != nullptr) && dataLen > 0) { 96 | putBytes(data, dataLen); 97 | } 98 | 99 | // Allocate space for the returned byte array and return it 100 | auto sz = size(); 101 | auto createRetData = std::make_unique(sz); 102 | setReadPos(0); 103 | getBytes(createRetData.get(), sz); 104 | 105 | return createRetData; 106 | } 107 | 108 | /** 109 | * Parse 110 | * Populate internal HTTPRequest variables by parsing the HTTP data 111 | * 112 | * @param True if successful. If false, sets parseErrorStr for reason of failure 113 | */ 114 | bool HTTPRequest::parse() { 115 | // Get elements from the initial line: \r\n 116 | std::string methodName = getStrElement(); 117 | if (methodName.empty()) { 118 | parseErrorStr = "Empty method"; 119 | return false; 120 | } 121 | 122 | // Convert the name to the internal enumeration number 123 | method = methodStrToInt(methodName); 124 | if (method == INVALID_METHOD) { 125 | parseErrorStr = "Invalid Method"; 126 | return false; 127 | } 128 | 129 | requestUri = getStrElement(); 130 | if (requestUri.empty()) { 131 | parseErrorStr = "No request URI"; 132 | return false; 133 | } 134 | 135 | version = getLine(); // End of the line, pull till \r\n 136 | if (version.empty()) { 137 | parseErrorStr = "HTTP version string was empty"; 138 | return false; 139 | } 140 | 141 | if (!version.starts_with("HTTP/1")) { 142 | parseErrorStr = "HTTP version was invalid"; 143 | return false; 144 | } 145 | 146 | // Optional - Validate the HTTP version. If there is a mismatch, discontinue parsing 147 | // if (strcmp(version.c_str(), HTTP_VERSION) != 0) { 148 | // parseErrorStr = "Supported HTTP version does not match"; 149 | // return false; 150 | // } 151 | 152 | // Parse and populate the headers map using the parseHeaders helper 153 | parseHeaders(); 154 | 155 | // Only POST and PUT can have Content (data after headers) 156 | if ((method != POST) && (method != PUT)) 157 | return true; 158 | 159 | // Parse the body of the message 160 | if (!parseBody()) 161 | return false; 162 | 163 | return true; 164 | } 165 | 166 | -------------------------------------------------------------------------------- /src/HTTPRequest.h: -------------------------------------------------------------------------------- 1 | /** 2 | ByteBuffer 3 | HTTPRequest.h 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #ifndef _HTTPREQUEST_H_ 20 | #define _HTTPREQUEST_H_ 21 | 22 | #include "HTTPMessage.h" 23 | 24 | #include 25 | 26 | class HTTPRequest final : public HTTPMessage { 27 | private: 28 | uint32_t method = 0; 29 | std::string requestUri = ""; 30 | 31 | public: 32 | HTTPRequest(); 33 | explicit HTTPRequest(std::string const& sData); 34 | explicit HTTPRequest(const uint8_t* pData, uint32_t len); 35 | ~HTTPRequest() override = default; 36 | 37 | std::unique_ptr create() override; 38 | bool parse() override; 39 | 40 | // Helper functions 41 | 42 | uint32_t methodStrToInt(std::string_view name) const; 43 | std::string methodIntToStr(uint32_t mid) const; 44 | 45 | // Info getters & setters 46 | void setMethod(uint32_t m) { 47 | method = m; 48 | } 49 | 50 | uint32_t getMethod() const { 51 | return method; 52 | } 53 | 54 | void setRequestUri(std::string_view u) { 55 | requestUri = u; 56 | } 57 | 58 | std::string getRequestUri() const { 59 | return requestUri; 60 | } 61 | }; 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /src/HTTPResponse.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | ByteBuffer 3 | HTTPResponse.cpp 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #include "HTTPMessage.h" 20 | #include "HTTPResponse.h" 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | HTTPResponse::HTTPResponse() : HTTPMessage() { 27 | } 28 | 29 | HTTPResponse::HTTPResponse(std::string const& sData) : HTTPMessage(sData) { 30 | } 31 | 32 | HTTPResponse::HTTPResponse(const uint8_t* pData, uint32_t len) : HTTPMessage(pData, len) { 33 | } 34 | 35 | /** 36 | * Determine the status code based on the parsed Responses reason string 37 | * The reason string is non standard so this method needs to change in order to handle 38 | * responses with different kinds of strings 39 | */ 40 | void HTTPResponse::determineStatusCode() { 41 | if (reason.contains("Continue")) { 42 | status = Status(CONTINUE); 43 | } else if (reason.contains("OK")) { 44 | status = Status(OK); 45 | } else if (reason.contains("Bad Request")) { 46 | status = Status(BAD_REQUEST); 47 | } else if (reason.contains("Not Found")) { 48 | status = Status(NOT_FOUND); 49 | } else if (reason.contains("Server Error")) { 50 | status = Status(SERVER_ERROR); 51 | } else if (reason.contains("Not Implemented")) { 52 | status = Status(NOT_IMPLEMENTED); 53 | } else { 54 | status = Status(NOT_IMPLEMENTED); 55 | } 56 | } 57 | 58 | /** 59 | * Determine the reason string based on the Response's status code 60 | */ 61 | void HTTPResponse::determineReasonStr() { 62 | switch (status) { 63 | case Status(CONTINUE): 64 | reason = "Continue"; 65 | break; 66 | case Status(OK): 67 | reason = "OK"; 68 | break; 69 | case Status(BAD_REQUEST): 70 | reason = "Bad Request"; 71 | break; 72 | case Status(NOT_FOUND): 73 | reason = "Not Found"; 74 | break; 75 | case Status(SERVER_ERROR): 76 | reason = "Internal Server Error"; 77 | break; 78 | case Status(NOT_IMPLEMENTED): 79 | reason = "Not Implemented"; 80 | break; 81 | default: 82 | break; 83 | } 84 | } 85 | 86 | /** 87 | * Create 88 | * Create and return a byte array of an HTTP response, built from the variables of this HTTPResponse 89 | * Caller will be responsible for cleaning up returned byte array 90 | * 91 | * @return Byte array of this HTTPResponse to be sent over the wire 92 | */ 93 | std::unique_ptr HTTPResponse::create() { 94 | // Clear the bytebuffer in the event this isn't the first call of create() 95 | clear(); 96 | 97 | // Insert the status line: \r\n 98 | putLine(std::format("{} {} {}", version, status, reason)); 99 | 100 | // Put all headers 101 | putHeaders(); 102 | 103 | // If theres body data, add it now 104 | if ((data != nullptr) && dataLen > 0) { 105 | putBytes(data, dataLen); 106 | } 107 | 108 | // Allocate space for the returned byte array and return it 109 | auto createRetData = std::make_unique(size()); 110 | setReadPos(0); 111 | getBytes(createRetData.get(), size()); 112 | 113 | return createRetData; 114 | } 115 | 116 | /** 117 | * Parse 118 | * Populate internal HTTPResponse variables by parsing the HTTP data 119 | * 120 | * @param True if successful. If false, sets parseErrorStr for reason of failure 121 | */ 122 | bool HTTPResponse::parse() { 123 | std::string statusstr; 124 | 125 | // Get elements from the status line: \r\n 126 | version = getStrElement(); 127 | statusstr = getStrElement(); 128 | determineStatusCode(); 129 | reason = getLine(); // Pull till \r\n termination 130 | 131 | // Optional - Validate the HTTP version. If there is a mismatch, discontinue parsing 132 | // if (strcmp(version.c_str(), HTTP_VERSION) != 0) { 133 | // parseErrorStr = "Supported HTTP version does not match"; 134 | // return false; 135 | // } 136 | 137 | // Parse and populate the headers map using the parseHeaders helper 138 | parseHeaders(); 139 | 140 | // If the body of the message 141 | if (!parseBody()) 142 | return false; 143 | 144 | return true; 145 | } 146 | 147 | 148 | -------------------------------------------------------------------------------- /src/HTTPResponse.h: -------------------------------------------------------------------------------- 1 | /** 2 | ByteBuffer 3 | HTTPResponse.h 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #ifndef _HTTPRESPONSE_H_ 20 | #define _HTTPRESPONSE_H_ 21 | 22 | #include "HTTPMessage.h" 23 | 24 | #include 25 | 26 | class HTTPResponse final : public HTTPMessage { 27 | private: 28 | // Response variables 29 | int32_t status = 0; 30 | std::string reason = ""; 31 | 32 | void determineReasonStr(); 33 | void determineStatusCode(); 34 | 35 | public: 36 | HTTPResponse(); 37 | explicit HTTPResponse(std::string const& sData); 38 | explicit HTTPResponse(const uint8_t* pData, uint32_t len); 39 | ~HTTPResponse() override = default; 40 | 41 | std::unique_ptr create() override; 42 | bool parse() override; 43 | 44 | // Accessors & Mutators 45 | void setStatus (int32_t scode) { 46 | status = scode; 47 | determineReasonStr(); 48 | } 49 | 50 | std::string getReason() const { 51 | return reason; 52 | } 53 | }; 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/HTTPServer.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | httpserver 3 | HTTPServer.cpp 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #include "HTTPServer.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #ifdef __linux__ 36 | #include // libkqueue Linux - only works if libkqueue is compiled from Github sources 37 | #else 38 | #include // kqueue BSD / OS X 39 | #endif 40 | 41 | /** 42 | * Server Constructor 43 | * Initialize state and server variables 44 | * 45 | * @param vhost_aliases List of hostnames the HTTP server will respond to 46 | * @param port Port the vhost listens on 47 | * @param diskpath Path to the folder the vhost serves up 48 | * @param drop_uid UID to setuid to after bind(). Ignored if 0 49 | * @param drop_gid GID to setgid to after bind(). Ignored if 0 50 | */ 51 | HTTPServer::HTTPServer(std::vector const& vhost_aliases, int32_t port, std::string const& diskpath, int32_t drop_uid, int32_t drop_gid) : listenPort(port), dropUid(drop_uid), dropGid(drop_gid) { 52 | 53 | std::cout << "Port: " << port << std::endl; 54 | std::cout << "Disk path: " << diskpath << std::endl; 55 | 56 | // Create a resource host serving the base path ./htdocs on disk 57 | auto resHost = std::make_shared(diskpath); 58 | hostList.push_back(resHost); 59 | 60 | // Always serve up localhost/127.0.0.1 (which is why we only added one ResourceHost to hostList above) 61 | vhosts.try_emplace(std::format("localhost:{}", listenPort), resHost); 62 | vhosts.try_emplace(std::format("127.0.0.1:{}", listenPort), resHost); 63 | 64 | // Setup the resource host serving htdocs to provide for the vhost aliases 65 | for (auto const& vh : vhost_aliases) { 66 | if (vh.length() >= 122) { 67 | std::cout << "vhost " << vh << " too long, skipping!" << std::endl; 68 | continue; 69 | } 70 | 71 | std::cout << "vhost: " << vh << std::endl; 72 | vhosts.try_emplace(std::format("{}:{}", vh, listenPort), resHost); 73 | } 74 | } 75 | 76 | /** 77 | * Server Destructor 78 | * Removes all resources created in the constructor 79 | */ 80 | HTTPServer::~HTTPServer() { 81 | hostList.clear(); 82 | vhosts.clear(); 83 | } 84 | 85 | /** 86 | * Start Server 87 | * Initialize the Server Socket by requesting a socket handle, binding, and going into a listening state 88 | * 89 | * @return True if initialization succeeded. False if otherwise 90 | */ 91 | bool HTTPServer::start() { 92 | canRun = false; 93 | 94 | // Create a handle for the listening socket, TCP 95 | listenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 96 | if (listenSocket == INVALID_SOCKET) { 97 | std::cout << "Could not create socket!" << std::endl; 98 | return false; 99 | } 100 | 101 | // Set socket as non blocking 102 | fcntl(listenSocket, F_SETFL, O_NONBLOCK); 103 | 104 | // Populate the server address structure 105 | // modify to support multiple address families (bottom): http://eradman.com/posts/kqueue-tcp.html 106 | memset(&serverAddr, 0, sizeof(struct sockaddr_in)); // clear the struct 107 | serverAddr.sin_family = AF_INET; // Family: IP protocol 108 | serverAddr.sin_port = htons(listenPort); // Set the port (convert from host to netbyte order) 109 | serverAddr.sin_addr.s_addr = INADDR_ANY; // Let OS intelligently select the server's host address 110 | 111 | // Bind: Assign the address to the socket 112 | if (bind(listenSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) != 0) { 113 | std::cout << "Failed to bind to the address!" << std::endl; 114 | return false; 115 | } 116 | 117 | // Optionally drop uid/gid if specified 118 | if (dropUid > 0 && dropGid > 0) { 119 | if (setgid(dropGid) != 0) { 120 | std::cout << "setgid to " << dropGid << " failed!" << std::endl; 121 | return false; 122 | } 123 | 124 | if (setuid(dropUid) != 0) { 125 | std::cout << "setuid to " << dropUid << " failed!" << std::endl; 126 | return false; 127 | } 128 | 129 | std::cout << "Successfully dropped uid to " << dropUid << " and gid to " << dropGid << std::endl; 130 | } 131 | 132 | // Listen: Put the socket in a listening state, ready to accept connections 133 | // Accept a backlog of the OS Maximum connections in the queue 134 | if (listen(listenSocket, SOMAXCONN) != 0) { 135 | std::cout << "Failed to put the socket in a listening state" << std::endl; 136 | return false; 137 | } 138 | 139 | // Setup kqueue 140 | kqfd = kqueue(); 141 | if (kqfd == -1) { 142 | std::cout << "Could not create the kernel event queue!" << std::endl; 143 | return false; 144 | } 145 | 146 | // Have kqueue watch the listen socket 147 | updateEvent(listenSocket, EVFILT_READ, EV_ADD, 0, 0, NULL); 148 | 149 | canRun = true; 150 | std::cout << "Server ready. Listening on port " << listenPort << "..." << std::endl; 151 | return true; 152 | } 153 | 154 | /** 155 | * Stop 156 | * Disconnect all clients and cleanup all server resources created in start() 157 | */ 158 | void HTTPServer::stop() { 159 | canRun = false; 160 | 161 | if (listenSocket != INVALID_SOCKET) { 162 | // Close all open connections and delete Client's from memory 163 | for (auto& [clfd, cl] : clientMap) 164 | disconnectClient(cl, false); 165 | 166 | // Clear the map 167 | clientMap.clear(); 168 | 169 | // Remove listening socket from kqueue 170 | updateEvent(listenSocket, EVFILT_READ, EV_DELETE, 0, 0, NULL); 171 | 172 | // Shudown the listening socket and release it to the OS 173 | shutdown(listenSocket, SHUT_RDWR); 174 | close(listenSocket); 175 | listenSocket = INVALID_SOCKET; 176 | } 177 | 178 | if (kqfd != -1) { 179 | close(kqfd); 180 | kqfd = -1; 181 | } 182 | 183 | std::cout << "Server shutdown!" << std::endl; 184 | } 185 | 186 | /** 187 | * Update Event 188 | * Update the kqueue by creating the appropriate kevent structure 189 | * See kqueue documentation for parameter descriptions 190 | */ 191 | void HTTPServer::updateEvent(int ident, short filter, u_short flags, u_int fflags, int32_t data, void* udata) { 192 | struct kevent kev; 193 | EV_SET(&kev, ident, filter, flags, fflags, data, udata); 194 | kevent(kqfd, &kev, 1, NULL, 0, NULL); 195 | } 196 | 197 | /** 198 | * Server Process 199 | * Main server processing function that checks for any new connections or data to read on 200 | * the listening socket 201 | */ 202 | void HTTPServer::process() { 203 | int32_t nev = 0; // Number of changed events returned by kevent 204 | 205 | while (canRun) { 206 | // Get a list of changed socket descriptors with a read event triggered in evList 207 | // Timeout set in the header 208 | nev = kevent(kqfd, NULL, 0, evList, QUEUE_SIZE, &kqTimeout); 209 | 210 | if (nev <= 0) 211 | continue; 212 | 213 | // Loop through only the sockets that have changed in the evList array 214 | for (int i = 0; i < nev; i++) { 215 | 216 | // A client is waiting to connect 217 | if (evList[i].ident == (uint32_t)listenSocket) { 218 | acceptConnection(); 219 | continue; 220 | } 221 | 222 | // Client descriptor has triggered an event 223 | auto cl = getClient(evList[i].ident); // ident contains the clients socket descriptor 224 | if (cl == nullptr) { 225 | std::cout << "Could not find client" << std::endl; 226 | // Remove socket events from kqueue 227 | updateEvent(evList[i].ident, EVFILT_READ, EV_DELETE, 0, 0, NULL); 228 | updateEvent(evList[i].ident, EVFILT_WRITE, EV_DELETE, 0, 0, NULL); 229 | 230 | // Close the socket descriptor 231 | close(evList[i].ident); 232 | 233 | continue; 234 | } 235 | 236 | // Client wants to disconnect 237 | if (evList[i].flags & EV_EOF) { 238 | disconnectClient(cl, true); 239 | continue; 240 | } 241 | 242 | if (evList[i].filter == EVFILT_READ) { 243 | // std::cout << "read filter " << evList[i].data << " bytes available" << std::endl; 244 | // Read and process any pending data on the wire 245 | readClient(cl, evList[i].data); // data contains the number of bytes waiting to be read 246 | 247 | // Have kqueue disable tracking of READ events and enable tracking of WRITE events 248 | updateEvent(evList[i].ident, EVFILT_READ, EV_DISABLE, 0, 0, NULL); 249 | updateEvent(evList[i].ident, EVFILT_WRITE, EV_ENABLE, 0, 0, NULL); 250 | } else if (evList[i].filter == EVFILT_WRITE) { 251 | // std::cout << "write filter with " << evList[i].data << " bytes available" << std::endl; 252 | // Write any pending data to the client - writeClient returns true if there is additional data to send in the client queue 253 | if (!writeClient(cl, evList[i].data)) { // data contains number of bytes that can be written 254 | // std::cout << "switch back to read filter" << std::endl; 255 | // If theres nothing more to send, Have kqueue disable tracking of WRITE events and enable tracking of READ events 256 | updateEvent(evList[i].ident, EVFILT_READ, EV_ENABLE, 0, 0, NULL); 257 | updateEvent(evList[i].ident, EVFILT_WRITE, EV_DISABLE, 0, 0, NULL); 258 | } 259 | } 260 | } // Event loop 261 | } // canRun 262 | } 263 | 264 | /** 265 | * Accept Connection 266 | * When a new connection is detected in runServer() this function is called. This attempts to accept the pending 267 | * connection, instance a Client object, and add to the client Map 268 | */ 269 | void HTTPServer::acceptConnection() { 270 | // Setup new client with prelim address info 271 | sockaddr_in clientAddr; 272 | int32_t clientAddrLen = sizeof(clientAddr); 273 | int32_t clfd = INVALID_SOCKET; 274 | 275 | // Accept the pending connection and retrive the client descriptor 276 | clfd = accept(listenSocket, (sockaddr*)&clientAddr, (socklen_t*)&clientAddrLen); 277 | if (clfd == INVALID_SOCKET) 278 | return; 279 | 280 | // Set socket as non blocking 281 | fcntl(clfd, F_SETFL, O_NONBLOCK); 282 | 283 | // Add kqueue event to track the new client socket for READ and WRITE events 284 | updateEvent(clfd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL); 285 | updateEvent(clfd, EVFILT_WRITE, EV_ADD | EV_DISABLE, 0, 0, NULL); // Disabled initially 286 | 287 | // Add the client object to the client map 288 | auto cl = std::make_unique(clfd, clientAddr); 289 | std::cout << "[" << cl->getClientIP() << "] connected" << std::endl; 290 | clientMap.try_emplace(clfd, std::move(cl)); 291 | } 292 | 293 | /** 294 | * Get Client 295 | * Lookup client based on the socket descriptor number in the clientMap 296 | * 297 | * @param clfd Client socket descriptor 298 | * @return Pointer to Client object if found. NULL otherwise 299 | */ 300 | std::shared_ptr HTTPServer::getClient(int clfd) { 301 | auto it = clientMap.find(clfd); 302 | 303 | // Client wasn't found 304 | if (it == clientMap.end()) 305 | return nullptr; 306 | 307 | // Return a pointer to the client object 308 | return it->second; 309 | } 310 | 311 | /** 312 | * Disconnect Client 313 | * Close the client's socket descriptor and release it from the FD map, client map, and memory 314 | * 315 | * @param cl Pointer to Client object 316 | * @param mapErase When true, remove the client from the client map. Needed if operations on the 317 | * client map are being performed and we don't want to remove the map entry right away 318 | */ 319 | void HTTPServer::disconnectClient(std::shared_ptr cl, bool mapErase) { 320 | if (cl == nullptr) 321 | return; 322 | 323 | std::cout << "[" << cl->getClientIP() << "] disconnected" << std::endl; 324 | 325 | // Remove socket events from kqueue 326 | updateEvent(cl->getSocket(), EVFILT_READ, EV_DELETE, 0, 0, NULL); 327 | updateEvent(cl->getSocket(), EVFILT_WRITE, EV_DELETE, 0, 0, NULL); 328 | 329 | // Close the socket descriptor 330 | close(cl->getSocket()); 331 | 332 | // Remove the client from the clientMap 333 | if (mapErase) 334 | clientMap.erase(cl->getSocket()); 335 | } 336 | 337 | /** 338 | * Read Client 339 | * Recieve data from a client that has indicated that it has data waiting. Pass recv'd data to handleRequest() 340 | * Also detect any errors in the state of the socket 341 | * 342 | * @param cl Pointer to Client that sent the data 343 | * @param data_len Number of bytes waiting to be read 344 | */ 345 | void HTTPServer::readClient(std::shared_ptr cl, int32_t data_len) { 346 | if (cl == nullptr) 347 | return; 348 | 349 | // If the read filter triggered with 0 bytes of data, client may want to disconnect 350 | // Set data_len to the Ethernet max MTU by default 351 | if (data_len <= 0) 352 | data_len = 1400; 353 | 354 | auto pData = std::make_unique(data_len); 355 | 356 | // Receive data on the wire into pData 357 | int32_t flags = 0; 358 | ssize_t lenRecv = recv(cl->getSocket(), pData.get(), data_len, flags); 359 | 360 | // Determine state of the client socket and act on it 361 | if (lenRecv == 0) { 362 | // Client closed the connection 363 | std::cout << "[" << cl->getClientIP() << "] has opted to close the connection" << std::endl; 364 | disconnectClient(cl, true); 365 | } else if (lenRecv < 0) { 366 | // Something went wrong with the connection 367 | // TODO: check perror() for the specific error message 368 | disconnectClient(cl, true); 369 | } else { 370 | // Data received: Place the data in an HTTPRequest and pass it to handleRequest for processing 371 | auto req = new HTTPRequest(pData.get(), lenRecv); 372 | handleRequest(cl, req); 373 | delete req; 374 | } 375 | } 376 | 377 | /** 378 | * Write Client 379 | * Client has indicated it is read for writing. Write avail_bytes number of bytes to the socket if the send queue has an item 380 | * 381 | * @param cl Pointer to Client that sent the data 382 | * @param avail_bytes Number of bytes available for writing in the send buffer 383 | */ 384 | bool HTTPServer::writeClient(std::shared_ptr cl, int32_t avail_bytes) { 385 | if (cl == nullptr) 386 | return false; 387 | 388 | int32_t actual_sent = 0; // Actual number of bytes sent as returned by send() 389 | int32_t attempt_sent = 0; // Bytes that we're attempting to send now 390 | 391 | // The amount of available bytes to write, reported by the OS, cant really be trusted... 392 | if (avail_bytes > 1400) { 393 | // If the available amount of data is greater than the Ethernet MTU, cap it 394 | avail_bytes = 1400; 395 | } else if (avail_bytes == 0) { 396 | // Sometimes OS reports 0 when its possible to send data - attempt to trickle data 397 | // OS will eventually increase avail_bytes 398 | avail_bytes = 64; 399 | } 400 | 401 | auto item = cl->nextInSendQueue(); 402 | if (item == nullptr) 403 | return false; 404 | 405 | const uint8_t* const pData = item->getRawDataPointer(); 406 | 407 | // Size of data left to send for the item 408 | int32_t remaining = item->getSize() - item->getOffset(); 409 | bool disconnect = item->getDisconnect(); 410 | 411 | if (avail_bytes >= remaining) { 412 | // Send buffer is bigger than we need, rest of item can be sent 413 | attempt_sent = remaining; 414 | } else { 415 | // Send buffer is smaller than we need, send the amount thats available 416 | attempt_sent = avail_bytes; 417 | } 418 | 419 | // Send the data and increment the offset by the actual amount sent 420 | actual_sent = send(cl->getSocket(), pData + (item->getOffset()), attempt_sent, 0); 421 | if (actual_sent >= 0) 422 | item->setOffset(item->getOffset() + actual_sent); 423 | else 424 | disconnect = true; 425 | 426 | // std::cout << "[" << cl->getClientIP() << "] was sent " << actual_sent << " bytes " << std::endl; 427 | 428 | // SendQueueItem isnt needed anymore. Dequeue and delete 429 | if (item->getOffset() >= item->getSize()) 430 | cl->dequeueFromSendQueue(); 431 | 432 | if (disconnect) { 433 | disconnectClient(cl, true); 434 | return false; 435 | } 436 | 437 | return true; 438 | } 439 | 440 | /** 441 | * Handle Request 442 | * Process an incoming request from a Client. Send request off to appropriate handler function 443 | * that corresponds to an HTTP operation (GET, HEAD etc) :) 444 | * 445 | * @param cl Client object where request originated from 446 | * @param req HTTPRequest object filled with raw packet data 447 | */ 448 | void HTTPServer::handleRequest(std::shared_ptr cl, HTTPRequest* const req) { 449 | // Parse the request 450 | // If there's an error, report it and send a server error in response 451 | if (!req->parse()) { 452 | std::cout << "[" << cl->getClientIP() << "] There was an error processing the request of type: " << req->methodIntToStr(req->getMethod()) << std::endl; 453 | std::cout << req->getParseError() << std::endl; 454 | sendStatusResponse(cl, Status(BAD_REQUEST)); 455 | return; 456 | } 457 | 458 | std::cout << "[" << cl->getClientIP() << "] " << req->methodIntToStr(req->getMethod()) << " " << req->getRequestUri() << std::endl; 459 | /*std::cout << "Headers:" << std::endl; 460 | for (uint32_t i = 0; i < req->getNumHeaders(); i++) { 461 | std::cout << "\t" << req->getHeaderStr(i) << std::endl; 462 | } 463 | std::cout << std::endl;*/ 464 | 465 | // Send the request to the correct handler function 466 | switch (req->getMethod()) { 467 | case Method(HEAD): 468 | case Method(GET): 469 | handleGet(cl, req); 470 | break; 471 | case Method(OPTIONS): 472 | handleOptions(cl, req); 473 | break; 474 | case Method(TRACE): 475 | handleTrace(cl, req); 476 | break; 477 | default: 478 | std::cout << "[" << cl->getClientIP() << "] Could not handle or determine request of type " << req->methodIntToStr(req->getMethod()) << std::endl; 479 | sendStatusResponse(cl, Status(NOT_IMPLEMENTED)); 480 | break; 481 | } 482 | } 483 | 484 | /** 485 | * Handle Get or Head 486 | * Process a GET or HEAD request to provide the client with an appropriate response 487 | * 488 | * @param cl Client requesting the resource 489 | * @param req State of the request 490 | */ 491 | void HTTPServer::handleGet(std::shared_ptr cl, const HTTPRequest* const req) { 492 | auto resHost = this->getResourceHostForRequest(req); 493 | 494 | // ResourceHost couldnt be determined or the Host specified by the client was invalid 495 | if (resHost == nullptr) { 496 | sendStatusResponse(cl, Status(BAD_REQUEST), "Invalid/No Host specified"); 497 | return; 498 | } 499 | 500 | // Check if the requested resource exists 501 | auto uri = req->getRequestUri(); 502 | auto r = resHost->getResource(uri); 503 | 504 | if (r != nullptr) { // Exists 505 | std::cout << "[" << cl->getClientIP() << "] " << "Sending file: " << uri << std::endl; 506 | 507 | auto resp = std::make_unique(); 508 | resp->setStatus(Status(OK)); 509 | resp->addHeader("Content-Type", r->getMimeType()); 510 | resp->addHeader("Content-Length", r->getSize()); 511 | 512 | // Only send a message body if it's a GET request. Never send a body for HEAD 513 | if (req->getMethod() == Method(GET)) 514 | resp->setData(r->getData(), r->getSize()); 515 | 516 | bool dc = false; 517 | 518 | // HTTP/1.0 should close the connection by default 519 | if (req->getVersion().compare(HTTP_VERSION_10) == 0) 520 | dc = true; 521 | 522 | // If Connection: close is specified, the connection should be terminated after the request is serviced 523 | if (auto con_val = req->getHeaderValue("Connection"); con_val.compare("close") == 0) 524 | dc = true; 525 | 526 | sendResponse(cl, std::move(resp), dc); 527 | } else { // Not found 528 | std::cout << "[" << cl->getClientIP() << "] " << "File not found: " << uri << std::endl; 529 | sendStatusResponse(cl, Status(NOT_FOUND)); 530 | } 531 | } 532 | 533 | /** 534 | * Handle Options 535 | * Process a OPTIONS request 536 | * OPTIONS: Return allowed capabilties for the server (*) or a particular resource 537 | * 538 | * @param cl Client requesting the resource 539 | * @param req State of the request 540 | */ 541 | void HTTPServer::handleOptions(std::shared_ptr cl, [[maybe_unused]] const HTTPRequest* const req) { 542 | // For now, we'll always return the capabilities of the server instead of figuring it out for each resource 543 | std::string allow = "HEAD, GET, OPTIONS, TRACE"; 544 | 545 | auto resp = std::make_unique(); 546 | resp->setStatus(Status(OK)); 547 | resp->addHeader("Allow", allow); 548 | resp->addHeader("Content-Length", "0"); // Required 549 | 550 | sendResponse(cl, std::move(resp), true); 551 | } 552 | 553 | /** 554 | * Handle Trace 555 | * Process a TRACE request 556 | * TRACE: send back the request as received by the server verbatim 557 | * 558 | * @param cl Client requesting the resource 559 | * @param req State of the request 560 | */ 561 | void HTTPServer::handleTrace(std::shared_ptr cl, HTTPRequest* const req) { 562 | // Get a byte array representation of the request 563 | uint32_t len = req->size(); 564 | auto buf = std::make_unique(len); 565 | req->setReadPos(0); // Set the read position at the beginning since the request has already been read to the end 566 | req->getBytes(buf.get(), len); 567 | 568 | // Send a response with the entire request as the body 569 | auto resp = std::make_unique(); 570 | resp->setStatus(Status(OK)); 571 | resp->addHeader("Content-Type", "message/http"); 572 | resp->addHeader("Content-Length", len); 573 | resp->setData(buf.get(), len); 574 | sendResponse(cl, std::move(resp), true); 575 | } 576 | 577 | /** 578 | * Send Status Response 579 | * Send a predefined HTTP status code response to the client consisting of 580 | * only the status code and required headers, then disconnect the client 581 | * 582 | * @param cl Client to send the status code to 583 | * @param status Status code corresponding to the enum in HTTPMessage.h 584 | * @param msg An additional message to append to the body text 585 | */ 586 | void HTTPServer::sendStatusResponse(std::shared_ptr cl, int32_t status, std::string const& msg) { 587 | auto resp = std::make_unique(); 588 | resp->setStatus(status); 589 | 590 | // Body message: Reason string + additional msg 591 | std::string body = resp->getReason(); 592 | if (msg.length() > 0) 593 | body += ": " + msg; 594 | 595 | uint32_t slen = body.length(); 596 | auto sdata = new uint8_t[slen]; 597 | memset(sdata, 0x00, slen); 598 | strncpy((char*)sdata, body.c_str(), slen); 599 | 600 | resp->addHeader("Content-Type", "text/plain"); 601 | resp->addHeader("Content-Length", slen); 602 | resp->setData(sdata, slen); 603 | 604 | sendResponse(cl, std::move(resp), true); 605 | } 606 | 607 | /** 608 | * Send Response 609 | * Send a generic HTTPResponse packet data to a particular Client 610 | * 611 | * @param cl Client to send data to 612 | * @param buf ByteBuffer containing data to be sent 613 | * @param disconnect Should the server disconnect the client after sending (Optional, default = false) 614 | */ 615 | void HTTPServer::sendResponse(std::shared_ptr cl, std::unique_ptr resp, bool disconnect) { 616 | // Server Header 617 | resp->addHeader("Server", "httpserver/1.0"); 618 | 619 | // Time stamp the response with the Date header 620 | std::string tstr; 621 | char tbuf[36] = {0}; 622 | time_t rawtime; 623 | struct tm ptm = {0}; 624 | time(&rawtime); 625 | if (gmtime_r(&rawtime, &ptm) != nullptr) { 626 | // Ex: Fri, 31 Dec 1999 23:59:59 GMT 627 | strftime(tbuf, 36, "%a, %d %b %Y %H:%M:%S GMT", &ptm); 628 | tstr = tbuf; 629 | resp->addHeader("Date", tstr); 630 | } 631 | 632 | // Include a Connection: close header if this is the final response sent by the server 633 | if (disconnect) 634 | resp->addHeader("Connection", "close"); 635 | 636 | // Get raw data by creating the response (we are responsible for cleaning it up in process()) 637 | // Add data to the Client's send queue 638 | cl->addToSendQueue(new SendQueueItem(resp->create(), resp->size(), disconnect)); 639 | } 640 | 641 | /** 642 | * Get Resource Host 643 | * Retrieve the appropriate ResourceHost instance based on the requested path 644 | * 645 | * @param req State of the request 646 | */ 647 | std::shared_ptr HTTPServer::getResourceHostForRequest(const HTTPRequest* const req) { 648 | // Determine the appropriate vhost 649 | std::string host = ""; 650 | 651 | // Retrieve the host specified in the request (Required for HTTP/1.1 compliance) 652 | if (req->getVersion().compare(HTTP_VERSION_11) == 0) { 653 | host = req->getHeaderValue("Host"); 654 | 655 | // All vhosts have the port appended, so need to append it to the host if it doesnt exist 656 | if (!host.contains(":")) { 657 | host.append(std::format(":{}", listenPort)); 658 | } 659 | 660 | auto it = vhosts.find(host); 661 | 662 | if (it != vhosts.end()) 663 | return it->second; 664 | } else { 665 | // Temporary: HTTP/1.0 are given the first ResouceHost in the hostList 666 | // TODO: Allow admin to specify a 'default resource host' 667 | if (!hostList.empty()) 668 | return hostList[0]; 669 | } 670 | 671 | return nullptr; 672 | } 673 | -------------------------------------------------------------------------------- /src/HTTPServer.h: -------------------------------------------------------------------------------- 1 | /** 2 | httpserver 3 | HTTPServer.h 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #ifndef _HTTPSERVER_H_ 20 | #define _HTTPSERVER_H_ 21 | 22 | #include "Client.h" 23 | #include "HTTPRequest.h" 24 | #include "HTTPResponse.h" 25 | #include "ResourceHost.h" 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #ifdef __linux__ 33 | #include // libkqueue Linux - only works if libkqueue is compiled from Github sources 34 | #else 35 | #include // kqueue BSD / OS X 36 | #endif 37 | 38 | constexpr int32_t INVALID_SOCKET = -1; 39 | constexpr uint32_t QUEUE_SIZE = 1024; 40 | 41 | class HTTPServer { 42 | // Server Socket 43 | int32_t listenPort; 44 | int32_t listenSocket = INVALID_SOCKET; // Descriptor for the listening socket 45 | struct sockaddr_in serverAddr; // Structure for the server address 46 | int32_t dropUid; // setuid to this after bind() 47 | int32_t dropGid; // setgid to this after bind() 48 | 49 | // Kqueue 50 | struct timespec kqTimeout = {2, 0}; // Block for 2 seconds and 0ns at the most 51 | int32_t kqfd = -1; // kqueue descriptor 52 | struct kevent evList[QUEUE_SIZE]; // Events that have triggered a filter in the kqueue (max QUEUE_SIZE at a time) 53 | 54 | // Client map, maps Socket descriptor to Client object 55 | std::unordered_map> clientMap; 56 | 57 | // Resources / File System 58 | std::vector> hostList; // Contains all ResourceHosts 59 | std::unordered_map, std::hash, std::equal_to<>> vhosts; // Virtual hosts. Maps a host string to a ResourceHost to service the request 60 | 61 | // Connection processing 62 | void updateEvent(int ident, short filter, u_short flags, u_int fflags, int32_t data, void* udata); 63 | void acceptConnection(); 64 | std::shared_ptr getClient(int clfd); 65 | void disconnectClient(std::shared_ptr cl, bool mapErase = true); 66 | void readClient(std::shared_ptr cl, int32_t data_len); // Client read event 67 | bool writeClient(std::shared_ptr cl, int32_t avail_bytes); // Client write event 68 | std::shared_ptr getResourceHostForRequest(const HTTPRequest* const req); 69 | 70 | // Request handling 71 | void handleRequest(std::shared_ptr cl, HTTPRequest* const req); 72 | void handleGet(std::shared_ptr cl, const HTTPRequest* const req); 73 | void handleOptions(std::shared_ptr cl, const HTTPRequest* const req); 74 | void handleTrace(std::shared_ptr cl, HTTPRequest* const req); 75 | 76 | // Response 77 | void sendStatusResponse(std::shared_ptr cl, int32_t status, std::string const& msg = ""); 78 | void sendResponse(std::shared_ptr cl, std::unique_ptr resp, bool disconnect); 79 | 80 | public: 81 | bool canRun = false; 82 | 83 | public: 84 | HTTPServer(std::vector const& vhost_aliases, int32_t port, std::string const& diskpath, int32_t drop_uid=0, int32_t drop_gid=0); 85 | ~HTTPServer(); 86 | 87 | bool start(); 88 | void stop(); 89 | 90 | // Main event loop 91 | void process(); 92 | }; 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /src/MimeTypes.inc: -------------------------------------------------------------------------------- 1 | {"ez", "application/andrew-inset"}, 2 | {"aw", "application/applixware"}, 3 | {"atom", "application/atom+xml"}, 4 | {"atomcat", "application/atomcat+xml"}, 5 | {"atomsvc", "application/atomsvc+xml"}, 6 | {"ccxml", "application/ccxml+xml"}, 7 | {"cdmia", "application/cdmi-capability"}, 8 | {"cdmic", "application/cdmi-container"}, 9 | {"cdmid", "application/cdmi-domain"}, 10 | {"cdmio", "application/cdmi-object"}, 11 | {"cdmiq", "application/cdmi-queue"}, 12 | {"cu", "application/cu-seeme"}, 13 | {"davmount", "application/davmount+xml"}, 14 | {"dbk", "application/docbook+xml"}, 15 | {"dssc", "application/dssc+der"}, 16 | {"xdssc", "application/dssc+xml"}, 17 | {"ecma", "application/ecmascript"}, 18 | {"emma", "application/emma+xml"}, 19 | {"epub", "application/epub+zip"}, 20 | {"exi", "application/exi"}, 21 | {"pfr", "application/font-tdpfr"}, 22 | {"gml", "application/gml+xml"}, 23 | {"gpx", "application/gpx+xml"}, 24 | {"gxf", "application/gxf"}, 25 | {"stk", "application/hyperstudio"}, 26 | {"ink", "application/inkml+xml"}, 27 | {"inkml", "application/inkml+xml"}, 28 | {"ipfix", "application/ipfix"}, 29 | {"jar", "application/java-archive"}, 30 | {"ser", "application/java-serialized-object"}, 31 | {"class", "application/java-vm"}, 32 | {"json", "application/json"}, 33 | {"jsonml", "application/jsonml+json"}, 34 | {"lostxml", "application/lost+xml"}, 35 | {"hqx", "application/mac-binhex40"}, 36 | {"cpt", "application/mac-compactpro"}, 37 | {"mads", "application/mads+xml"}, 38 | {"mrc", "application/marc"}, 39 | {"mrcx", "application/marcxml+xml"}, 40 | {"ma", "application/mathematica"}, 41 | {"nb", "application/mathematica"}, 42 | {"mb", "application/mathematica"}, 43 | {"mathml", "application/mathml+xml"}, 44 | {"mbox", "application/mbox"}, 45 | {"mscml", "application/mediaservercontrol+xml"}, 46 | {"metalink", "application/metalink+xml"}, 47 | {"meta4", "application/metalink4+xml"}, 48 | {"mets", "application/mets+xml"}, 49 | {"mods", "application/mods+xml"}, 50 | {"m21", "application/mp21"}, 51 | {"mp21", "application/mp21"}, 52 | {"mp4s", "application/mp4"}, 53 | {"doc", "application/msword"}, 54 | {"dot", "application/msword"}, 55 | {"mxf", "application/mxf"}, 56 | {"bin", "application/octet-stream"}, 57 | {"dms", "application/octet-stream"}, 58 | {"lrf", "application/octet-stream"}, 59 | {"mar", "application/octet-stream"}, 60 | {"so", "application/octet-stream"}, 61 | {"dist", "application/octet-stream"}, 62 | {"distz", "application/octet-stream"}, 63 | {"pkg", "application/octet-stream"}, 64 | {"bpk", "application/octet-stream"}, 65 | {"dump", "application/octet-stream"}, 66 | {"elc", "application/octet-stream"}, 67 | {"deploy", "application/octet-stream"}, 68 | {"oda", "application/oda"}, 69 | {"opf", "application/oebps-package+xml"}, 70 | {"ogx", "application/ogg"}, 71 | {"omdoc", "application/omdoc+xml"}, 72 | {"onetoc", "application/onenote"}, 73 | {"onetoc2", "application/onenote"}, 74 | {"onetmp", "application/onenote"}, 75 | {"onepkg", "application/onenote"}, 76 | {"oxps", "application/oxps"}, 77 | {"xer", "application/patch-ops-error+xml"}, 78 | {"pdf", "application/pdf"}, 79 | {"pgp", "application/pgp-encrypted"}, 80 | {"asc", "application/pgp-signature"}, 81 | {"sig", "application/pgp-signature"}, 82 | {"prf", "application/pics-rules"}, 83 | {"p10", "application/pkcs10"}, 84 | {"p7m", "application/pkcs7-mime"}, 85 | {"p7c", "application/pkcs7-mime"}, 86 | {"p7s", "application/pkcs7-signature"}, 87 | {"p8", "application/pkcs8"}, 88 | {"ac", "application/pkix-attr-cert"}, 89 | {"cer", "application/pkix-cert"}, 90 | {"crl", "application/pkix-crl"}, 91 | {"pkipath", "application/pkix-pkipath"}, 92 | {"pki", "application/pkixcmp"}, 93 | {"pls", "application/pls+xml"}, 94 | {"ai", "application/postscript"}, 95 | {"eps", "application/postscript"}, 96 | {"ps", "application/postscript"}, 97 | {"cww", "application/prs.cww"}, 98 | {"pskcxml", "application/pskc+xml"}, 99 | {"rdf", "application/rdf+xml"}, 100 | {"rif", "application/reginfo+xml"}, 101 | {"rnc", "application/relax-ng-compact-syntax"}, 102 | {"rl", "application/resource-lists+xml"}, 103 | {"rld", "application/resource-lists-diff+xml"}, 104 | {"rs", "application/rls-services+xml"}, 105 | {"gbr", "application/rpki-ghostbusters"}, 106 | {"mft", "application/rpki-manifest"}, 107 | {"roa", "application/rpki-roa"}, 108 | {"rsd", "application/rsd+xml"}, 109 | {"rss", "application/rss+xml"}, 110 | {"rtf", "application/rtf"}, 111 | {"sbml", "application/sbml+xml"}, 112 | {"scq", "application/scvp-cv-request"}, 113 | {"scs", "application/scvp-cv-response"}, 114 | {"spq", "application/scvp-vp-request"}, 115 | {"spp", "application/scvp-vp-response"}, 116 | {"sdp", "application/sdp"}, 117 | {"setpay", "application/set-payment-initiation"}, 118 | {"setreg", "application/set-registration-initiation"}, 119 | {"shf", "application/shf+xml"}, 120 | {"smi", "application/smil+xml"}, 121 | {"smil", "application/smil+xml"}, 122 | {"rq", "application/sparql-query"}, 123 | {"srx", "application/sparql-results+xml"}, 124 | {"gram", "application/srgs"}, 125 | {"grxml", "application/srgs+xml"}, 126 | {"sru", "application/sru+xml"}, 127 | {"ssdl", "application/ssdl+xml"}, 128 | {"ssml", "application/ssml+xml"}, 129 | {"tei", "application/tei+xml"}, 130 | {"teicorpus", "application/tei+xml"}, 131 | {"tfi", "application/thraud+xml"}, 132 | {"tsd", "application/timestamped-data"}, 133 | {"plb", "application/vnd.3gpp.pic-bw-large"}, 134 | {"psb", "application/vnd.3gpp.pic-bw-small"}, 135 | {"pvb", "application/vnd.3gpp.pic-bw-var"}, 136 | {"tcap", "application/vnd.3gpp2.tcap"}, 137 | {"pwn", "application/vnd.3m.post-it-notes"}, 138 | {"aso", "application/vnd.accpac.simply.aso"}, 139 | {"imp", "application/vnd.accpac.simply.imp"}, 140 | {"acu", "application/vnd.acucobol"}, 141 | {"atc", "application/vnd.acucorp"}, 142 | {"acutc", "application/vnd.acucorp"}, 143 | {"air", "application/vnd.adobe.air-application-installer-package+zip"}, 144 | {"fcdt", "application/vnd.adobe.formscentral.fcdt"}, 145 | {"fxp", "application/vnd.adobe.fxp"}, 146 | {"fxpl", "application/vnd.adobe.fxp"}, 147 | {"xdp", "application/vnd.adobe.xdp+xml"}, 148 | {"xfdf", "application/vnd.adobe.xfdf"}, 149 | {"ahead", "application/vnd.ahead.space"}, 150 | {"azf", "application/vnd.airzip.filesecure.azf"}, 151 | {"azs", "application/vnd.airzip.filesecure.azs"}, 152 | {"azw", "application/vnd.amazon.ebook"}, 153 | {"acc", "application/vnd.americandynamics.acc"}, 154 | {"ami", "application/vnd.amiga.ami"}, 155 | {"apk", "application/vnd.android.package-archive"}, 156 | {"cii", "application/vnd.anser-web-certificate-issue-initiation"}, 157 | {"fti", "application/vnd.anser-web-funds-transfer-initiation"}, 158 | {"atx", "application/vnd.antix.game-component"}, 159 | {"mpkg", "application/vnd.apple.installer+xml"}, 160 | {"m3u8", "application/vnd.apple.mpegurl"}, 161 | {"swi", "application/vnd.aristanetworks.swi"}, 162 | {"iota", "application/vnd.astraea-software.iota"}, 163 | {"aep", "application/vnd.audiograph"}, 164 | {"mpm", "application/vnd.blueice.multipass"}, 165 | {"bmi", "application/vnd.bmi"}, 166 | {"rep", "application/vnd.businessobjects"}, 167 | {"cdxml", "application/vnd.chemdraw+xml"}, 168 | {"mmd", "application/vnd.chipnuts.karaoke-mmd"}, 169 | {"cdy", "application/vnd.cinderella"}, 170 | {"cla", "application/vnd.claymore"}, 171 | {"rp9", "application/vnd.cloanto.rp9"}, 172 | {"c4g", "application/vnd.clonk.c4group"}, 173 | {"c4d", "application/vnd.clonk.c4group"}, 174 | {"c4f", "application/vnd.clonk.c4group"}, 175 | {"c4p", "application/vnd.clonk.c4group"}, 176 | {"c4u", "application/vnd.clonk.c4group"}, 177 | {"c11amc", "application/vnd.cluetrust.cartomobile-config"}, 178 | {"c11amz", "application/vnd.cluetrust.cartomobile-config-pkg"}, 179 | {"csp", "application/vnd.commonspace"}, 180 | {"cdbcmsg", "application/vnd.contact.cmsg"}, 181 | {"cmc", "application/vnd.cosmocaller"}, 182 | {"clkx", "application/vnd.crick.clicker"}, 183 | {"clkk", "application/vnd.crick.clicker.keyboard"}, 184 | {"clkp", "application/vnd.crick.clicker.palette"}, 185 | {"clkt", "application/vnd.crick.clicker.template"}, 186 | {"clkw", "application/vnd.crick.clicker.wordbank"}, 187 | {"wbs", "application/vnd.criticaltools.wbs+xml"}, 188 | {"pml", "application/vnd.ctc-posml"}, 189 | {"ppd", "application/vnd.cups-ppd"}, 190 | {"car", "application/vnd.curl.car"}, 191 | {"pcurl", "application/vnd.curl.pcurl"}, 192 | {"dart", "application/vnd.dart"}, 193 | {"rdz", "application/vnd.data-vision.rdz"}, 194 | {"uvf", "application/vnd.dece.data"}, 195 | {"uvvf", "application/vnd.dece.data"}, 196 | {"uvd", "application/vnd.dece.data"}, 197 | {"uvvd", "application/vnd.dece.data"}, 198 | {"uvt", "application/vnd.dece.ttml+xml"}, 199 | {"uvvt", "application/vnd.dece.ttml+xml"}, 200 | {"uvx", "application/vnd.dece.unspecified"}, 201 | {"uvvx", "application/vnd.dece.unspecified"}, 202 | {"uvz", "application/vnd.dece.zip"}, 203 | {"uvvz", "application/vnd.dece.zip"}, 204 | {"fe_launch", "application/vnd.denovo.fcselayout-link"}, 205 | {"dna", "application/vnd.dna"}, 206 | {"mlp", "application/vnd.dolby.mlp"}, 207 | {"dpg", "application/vnd.dpgraph"}, 208 | {"dfac", "application/vnd.dreamfactory"}, 209 | {"kpxx", "application/vnd.ds-keypoint"}, 210 | {"ait", "application/vnd.dvb.ait"}, 211 | {"svc", "application/vnd.dvb.service"}, 212 | {"geo", "application/vnd.dynageo"}, 213 | {"mag", "application/vnd.ecowin.chart"}, 214 | {"nml", "application/vnd.enliven"}, 215 | {"esf", "application/vnd.epson.esf"}, 216 | {"msf", "application/vnd.epson.msf"}, 217 | {"qam", "application/vnd.epson.quickanime"}, 218 | {"slt", "application/vnd.epson.salt"}, 219 | {"ssf", "application/vnd.epson.ssf"}, 220 | {"es3", "application/vnd.eszigno3+xml"}, 221 | {"et3", "application/vnd.eszigno3+xml"}, 222 | {"ez2", "application/vnd.ezpix-album"}, 223 | {"ez3", "application/vnd.ezpix-package"}, 224 | {"fdf", "application/vnd.fdf"}, 225 | {"mseed", "application/vnd.fdsn.mseed"}, 226 | {"seed", "application/vnd.fdsn.seed"}, 227 | {"dataless", "application/vnd.fdsn.seed"}, 228 | {"gph", "application/vnd.flographit"}, 229 | {"ftc", "application/vnd.fluxtime.clip"}, 230 | {"fm", "application/vnd.framemaker"}, 231 | {"frame", "application/vnd.framemaker"}, 232 | {"maker", "application/vnd.framemaker"}, 233 | {"book", "application/vnd.framemaker"}, 234 | {"fnc", "application/vnd.frogans.fnc"}, 235 | {"ltf", "application/vnd.frogans.ltf"}, 236 | {"fsc", "application/vnd.fsc.weblaunch"}, 237 | {"oas", "application/vnd.fujitsu.oasys"}, 238 | {"oa2", "application/vnd.fujitsu.oasys2"}, 239 | {"oa3", "application/vnd.fujitsu.oasys3"}, 240 | {"fg5", "application/vnd.fujitsu.oasysgp"}, 241 | {"bh2", "application/vnd.fujitsu.oasysprs"}, 242 | {"ddd", "application/vnd.fujixerox.ddd"}, 243 | {"xdw", "application/vnd.fujixerox.docuworks"}, 244 | {"xbd", "application/vnd.fujixerox.docuworks.binder"}, 245 | {"fzs", "application/vnd.fuzzysheet"}, 246 | {"txd", "application/vnd.genomatix.tuxedo"}, 247 | {"ggb", "application/vnd.geogebra.file"}, 248 | {"ggs", "application/vnd.geogebra.slides"}, 249 | {"ggt", "application/vnd.geogebra.tool"}, 250 | {"gex", "application/vnd.geometry-explorer"}, 251 | {"gre", "application/vnd.geometry-explorer"}, 252 | {"gxt", "application/vnd.geonext"}, 253 | {"g2w", "application/vnd.geoplan"}, 254 | {"g3w", "application/vnd.geospace"}, 255 | {"gmx", "application/vnd.gmx"}, 256 | {"kml", "application/vnd.google-earth.kml+xml"}, 257 | {"kmz", "application/vnd.google-earth.kmz"}, 258 | {"gqf", "application/vnd.grafeq"}, 259 | {"gqs", "application/vnd.grafeq"}, 260 | {"gac", "application/vnd.groove-account"}, 261 | {"ghf", "application/vnd.groove-help"}, 262 | {"gim", "application/vnd.groove-identity-message"}, 263 | {"grv", "application/vnd.groove-injector"}, 264 | {"gtm", "application/vnd.groove-tool-message"}, 265 | {"tpl", "application/vnd.groove-tool-template"}, 266 | {"vcg", "application/vnd.groove-vcard"}, 267 | {"hal", "application/vnd.hal+xml"}, 268 | {"zmm", "application/vnd.handheld-entertainment+xml"}, 269 | {"hbci", "application/vnd.hbci"}, 270 | {"les", "application/vnd.hhe.lesson-player"}, 271 | {"hpgl", "application/vnd.hp-hpgl"}, 272 | {"hpid", "application/vnd.hp-hpid"}, 273 | {"hps", "application/vnd.hp-hps"}, 274 | {"jlt", "application/vnd.hp-jlyt"}, 275 | {"pcl", "application/vnd.hp-pcl"}, 276 | {"pclxl", "application/vnd.hp-pclxl"}, 277 | {"sfd-hdstx", "application/vnd.hydrostatix.sof-data"}, 278 | {"mpy", "application/vnd.ibm.minipay"}, 279 | {"afp", "application/vnd.ibm.modcap"}, 280 | {"listafp", "application/vnd.ibm.modcap"}, 281 | {"list3820", "application/vnd.ibm.modcap"}, 282 | {"irm", "application/vnd.ibm.rights-management"}, 283 | {"sc", "application/vnd.ibm.secure-container"}, 284 | {"icc", "application/vnd.iccprofile"}, 285 | {"icm", "application/vnd.iccprofile"}, 286 | {"igl", "application/vnd.igloader"}, 287 | {"ivp", "application/vnd.immervision-ivp"}, 288 | {"ivu", "application/vnd.immervision-ivu"}, 289 | {"igm", "application/vnd.insors.igm"}, 290 | {"xpw", "application/vnd.intercon.formnet"}, 291 | {"xpx", "application/vnd.intercon.formnet"}, 292 | {"i2g", "application/vnd.intergeo"}, 293 | {"qbo", "application/vnd.intu.qbo"}, 294 | {"qfx", "application/vnd.intu.qfx"}, 295 | {"rcprofile", "application/vnd.ipunplugged.rcprofile"}, 296 | {"irp", "application/vnd.irepository.package+xml"}, 297 | {"xpr", "application/vnd.is-xpr"}, 298 | {"fcs", "application/vnd.isac.fcs"}, 299 | {"jam", "application/vnd.jam"}, 300 | {"rms", "application/vnd.jcp.javame.midlet-rms"}, 301 | {"jisp", "application/vnd.jisp"}, 302 | {"joda", "application/vnd.joost.joda-archive"}, 303 | {"ktz", "application/vnd.kahootz"}, 304 | {"ktr", "application/vnd.kahootz"}, 305 | {"karbon", "application/vnd.kde.karbon"}, 306 | {"chrt", "application/vnd.kde.kchart"}, 307 | {"kfo", "application/vnd.kde.kformula"}, 308 | {"flw", "application/vnd.kde.kivio"}, 309 | {"kon", "application/vnd.kde.kontour"}, 310 | {"kpr", "application/vnd.kde.kpresenter"}, 311 | {"kpt", "application/vnd.kde.kpresenter"}, 312 | {"ksp", "application/vnd.kde.kspread"}, 313 | {"kwd", "application/vnd.kde.kword"}, 314 | {"kwt", "application/vnd.kde.kword"}, 315 | {"htke", "application/vnd.kenameaapp"}, 316 | {"kia", "application/vnd.kidspiration"}, 317 | {"kne", "application/vnd.kinar"}, 318 | {"knp", "application/vnd.kinar"}, 319 | {"skp", "application/vnd.koan"}, 320 | {"skd", "application/vnd.koan"}, 321 | {"skt", "application/vnd.koan"}, 322 | {"skm", "application/vnd.koan"}, 323 | {"sse", "application/vnd.kodak-descriptor"}, 324 | {"lasxml", "application/vnd.las.las+xml"}, 325 | {"lbd", "application/vnd.llamagraphics.life-balance.desktop"}, 326 | {"lbe", "application/vnd.llamagraphics.life-balance.exchange+xml"}, 327 | {"123", "application/vnd.lotus-1-2-3"}, 328 | {"apr", "application/vnd.lotus-approach"}, 329 | {"pre", "application/vnd.lotus-freelance"}, 330 | {"nsf", "application/vnd.lotus-notes"}, 331 | {"org", "application/vnd.lotus-organizer"}, 332 | {"scm", "application/vnd.lotus-screencam"}, 333 | {"lwp", "application/vnd.lotus-wordpro"}, 334 | {"portpkg", "application/vnd.macports.portpkg"}, 335 | {"mcd", "application/vnd.mcd"}, 336 | {"mc1", "application/vnd.medcalcdata"}, 337 | {"cdkey", "application/vnd.mediastation.cdkey"}, 338 | {"mwf", "application/vnd.mfer"}, 339 | {"mfm", "application/vnd.mfmp"}, 340 | {"flo", "application/vnd.micrografx.flo"}, 341 | {"igx", "application/vnd.micrografx.igx"}, 342 | {"mif", "application/vnd.mif"}, 343 | {"daf", "application/vnd.mobius.daf"}, 344 | {"dis", "application/vnd.mobius.dis"}, 345 | {"mbk", "application/vnd.mobius.mbk"}, 346 | {"mqy", "application/vnd.mobius.mqy"}, 347 | {"msl", "application/vnd.mobius.msl"}, 348 | {"plc", "application/vnd.mobius.plc"}, 349 | {"txf", "application/vnd.mobius.txf"}, 350 | {"mpn", "application/vnd.mophun.application"}, 351 | {"mpc", "application/vnd.mophun.certificate"}, 352 | {"xul", "application/vnd.mozilla.xul+xml"}, 353 | {"cil", "application/vnd.ms-artgalry"}, 354 | {"cab", "application/vnd.ms-cab-compressed"}, 355 | {"xls", "application/vnd.ms-excel"}, 356 | {"xlm", "application/vnd.ms-excel"}, 357 | {"xla", "application/vnd.ms-excel"}, 358 | {"xlc", "application/vnd.ms-excel"}, 359 | {"xlt", "application/vnd.ms-excel"}, 360 | {"xlw", "application/vnd.ms-excel"}, 361 | {"xlam", "application/vnd.ms-excel.addin.macroenabled.12"}, 362 | {"xlsb", "application/vnd.ms-excel.sheet.binary.macroenabled.12"}, 363 | {"xlsm", "application/vnd.ms-excel.sheet.macroenabled.12"}, 364 | {"xltm", "application/vnd.ms-excel.template.macroenabled.12"}, 365 | {"eot", "application/vnd.ms-fontobject"}, 366 | {"chm", "application/vnd.ms-htmlhelp"}, 367 | {"ims", "application/vnd.ms-ims"}, 368 | {"lrm", "application/vnd.ms-lrm"}, 369 | {"thmx", "application/vnd.ms-officetheme"}, 370 | {"cat", "application/vnd.ms-pki.seccat"}, 371 | {"stl", "application/vnd.ms-pki.stl"}, 372 | {"ppt", "application/vnd.ms-powerpoint"}, 373 | {"pps", "application/vnd.ms-powerpoint"}, 374 | {"pot", "application/vnd.ms-powerpoint"}, 375 | {"ppam", "application/vnd.ms-powerpoint.addin.macroenabled.12"}, 376 | {"pptm", "application/vnd.ms-powerpoint.presentation.macroenabled.12"}, 377 | {"sldm", "application/vnd.ms-powerpoint.slide.macroenabled.12"}, 378 | {"ppsm", "application/vnd.ms-powerpoint.slideshow.macroenabled.12"}, 379 | {"potm", "application/vnd.ms-powerpoint.template.macroenabled.12"}, 380 | {"mpp", "application/vnd.ms-project"}, 381 | {"mpt", "application/vnd.ms-project"}, 382 | {"docm", "application/vnd.ms-word.document.macroenabled.12"}, 383 | {"dotm", "application/vnd.ms-word.template.macroenabled.12"}, 384 | {"wps", "application/vnd.ms-works"}, 385 | {"wks", "application/vnd.ms-works"}, 386 | {"wcm", "application/vnd.ms-works"}, 387 | {"wdb", "application/vnd.ms-works"}, 388 | {"wpl", "application/vnd.ms-wpl"}, 389 | {"xps", "application/vnd.ms-xpsdocument"}, 390 | {"mseq", "application/vnd.mseq"}, 391 | {"mus", "application/vnd.musician"}, 392 | {"msty", "application/vnd.muvee.style"}, 393 | {"taglet", "application/vnd.mynfc"}, 394 | {"nlu", "application/vnd.neurolanguage.nlu"}, 395 | {"ntf", "application/vnd.nitf"}, 396 | {"nitf", "application/vnd.nitf"}, 397 | {"nnd", "application/vnd.noblenet-directory"}, 398 | {"nns", "application/vnd.noblenet-sealer"}, 399 | {"nnw", "application/vnd.noblenet-web"}, 400 | {"ngdat", "application/vnd.nokia.n-gage.data"}, 401 | {"n-gage", "application/vnd.nokia.n-gage.symbian.install"}, 402 | {"rpst", "application/vnd.nokia.radio-preset"}, 403 | {"rpss", "application/vnd.nokia.radio-presets"}, 404 | {"edm", "application/vnd.novadigm.edm"}, 405 | {"edx", "application/vnd.novadigm.edx"}, 406 | {"ext", "application/vnd.novadigm.ext"}, 407 | {"odc", "application/vnd.oasis.opendocument.chart"}, 408 | {"otc", "application/vnd.oasis.opendocument.chart-template"}, 409 | {"odb", "application/vnd.oasis.opendocument.database"}, 410 | {"odf", "application/vnd.oasis.opendocument.formula"}, 411 | {"odft", "application/vnd.oasis.opendocument.formula-template"}, 412 | {"odg", "application/vnd.oasis.opendocument.graphics"}, 413 | {"otg", "application/vnd.oasis.opendocument.graphics-template"}, 414 | {"odi", "application/vnd.oasis.opendocument.image"}, 415 | {"oti", "application/vnd.oasis.opendocument.image-template"}, 416 | {"odp", "application/vnd.oasis.opendocument.presentation"}, 417 | {"otp", "application/vnd.oasis.opendocument.presentation-template"}, 418 | {"ods", "application/vnd.oasis.opendocument.spreadsheet"}, 419 | {"ots", "application/vnd.oasis.opendocument.spreadsheet-template"}, 420 | {"odt", "application/vnd.oasis.opendocument.text"}, 421 | {"odm", "application/vnd.oasis.opendocument.text-master"}, 422 | {"ott", "application/vnd.oasis.opendocument.text-template"}, 423 | {"oth", "application/vnd.oasis.opendocument.text-web"}, 424 | {"xo", "application/vnd.olpc-sugar"}, 425 | {"dd2", "application/vnd.oma.dd2+xml"}, 426 | {"oxt", "application/vnd.openofficeorg.extension"}, 427 | {"pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"}, 428 | {"sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide"}, 429 | {"ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow"}, 430 | {"potx", "application/vnd.openxmlformats-officedocument.presentationml.template"}, 431 | {"xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}, 432 | {"xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template"}, 433 | {"docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"}, 434 | {"dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template"}, 435 | {"mgp", "application/vnd.osgeo.mapguide.package"}, 436 | {"dp", "application/vnd.osgi.dp"}, 437 | {"esa", "application/vnd.osgi.subsystem"}, 438 | {"pdb", "application/vnd.palm"}, 439 | {"pqa", "application/vnd.palm"}, 440 | {"oprc", "application/vnd.palm"}, 441 | {"paw", "application/vnd.pawaafile"}, 442 | {"str", "application/vnd.pg.format"}, 443 | {"ei6", "application/vnd.pg.osasli"}, 444 | {"efif", "application/vnd.picsel"}, 445 | {"wg", "application/vnd.pmi.widget"}, 446 | {"plf", "application/vnd.pocketlearn"}, 447 | {"pbd", "application/vnd.powerbuilder6"}, 448 | {"box", "application/vnd.previewsystems.box"}, 449 | {"mgz", "application/vnd.proteus.magazine"}, 450 | {"qps", "application/vnd.publishare-delta-tree"}, 451 | {"ptid", "application/vnd.pvi.ptid1"}, 452 | {"qxd", "application/vnd.quark.quarkxpress"}, 453 | {"qxt", "application/vnd.quark.quarkxpress"}, 454 | {"qwd", "application/vnd.quark.quarkxpress"}, 455 | {"qwt", "application/vnd.quark.quarkxpress"}, 456 | {"qxl", "application/vnd.quark.quarkxpress"}, 457 | {"qxb", "application/vnd.quark.quarkxpress"}, 458 | {"bed", "application/vnd.realvnc.bed"}, 459 | {"mxl", "application/vnd.recordare.musicxml"}, 460 | {"musicxml", "application/vnd.recordare.musicxml+xml"}, 461 | {"cryptonote", "application/vnd.rig.cryptonote"}, 462 | {"cod", "application/vnd.rim.cod"}, 463 | {"rm", "application/vnd.rn-realmedia"}, 464 | {"rmvb", "application/vnd.rn-realmedia-vbr"}, 465 | {"link66", "application/vnd.route66.link66+xml"}, 466 | {"st", "application/vnd.sailingtracker.track"}, 467 | {"see", "application/vnd.seemail"}, 468 | {"sema", "application/vnd.sema"}, 469 | {"semd", "application/vnd.semd"}, 470 | {"semf", "application/vnd.semf"}, 471 | {"ifm", "application/vnd.shana.informed.formdata"}, 472 | {"itp", "application/vnd.shana.informed.formtemplate"}, 473 | {"iif", "application/vnd.shana.informed.interchange"}, 474 | {"ipk", "application/vnd.shana.informed.package"}, 475 | {"twd", "application/vnd.simtech-mindmapper"}, 476 | {"twds", "application/vnd.simtech-mindmapper"}, 477 | {"mmf", "application/vnd.smaf"}, 478 | {"teacher", "application/vnd.smart.teacher"}, 479 | {"sdkm", "application/vnd.solent.sdkm+xml"}, 480 | {"sdkd", "application/vnd.solent.sdkm+xml"}, 481 | {"dxp", "application/vnd.spotfire.dxp"}, 482 | {"sfs", "application/vnd.spotfire.sfs"}, 483 | {"sdc", "application/vnd.stardivision.calc"}, 484 | {"sda", "application/vnd.stardivision.draw"}, 485 | {"sdd", "application/vnd.stardivision.impress"}, 486 | {"smf", "application/vnd.stardivision.math"}, 487 | {"sdw", "application/vnd.stardivision.writer"}, 488 | {"vor", "application/vnd.stardivision.writer"}, 489 | {"sgl", "application/vnd.stardivision.writer-global"}, 490 | {"smzip", "application/vnd.stepmania.package"}, 491 | {"sm", "application/vnd.stepmania.stepchart"}, 492 | {"sxc", "application/vnd.sun.xml.calc"}, 493 | {"stc", "application/vnd.sun.xml.calc.template"}, 494 | {"sxd", "application/vnd.sun.xml.draw"}, 495 | {"std", "application/vnd.sun.xml.draw.template"}, 496 | {"sxi", "application/vnd.sun.xml.impress"}, 497 | {"sti", "application/vnd.sun.xml.impress.template"}, 498 | {"sxm", "application/vnd.sun.xml.math"}, 499 | {"sxw", "application/vnd.sun.xml.writer"}, 500 | {"sxg", "application/vnd.sun.xml.writer.global"}, 501 | {"stw", "application/vnd.sun.xml.writer.template"}, 502 | {"sus", "application/vnd.sus-calendar"}, 503 | {"susp", "application/vnd.sus-calendar"}, 504 | {"svd", "application/vnd.svd"}, 505 | {"sis", "application/vnd.symbian.install"}, 506 | {"sisx", "application/vnd.symbian.install"}, 507 | {"xsm", "application/vnd.syncml+xml"}, 508 | {"bdm", "application/vnd.syncml.dm+wbxml"}, 509 | {"xdm", "application/vnd.syncml.dm+xml"}, 510 | {"tao", "application/vnd.tao.intent-module-archive"}, 511 | {"pcap", "application/vnd.tcpdump.pcap"}, 512 | {"cap", "application/vnd.tcpdump.pcap"}, 513 | {"dmp", "application/vnd.tcpdump.pcap"}, 514 | {"tmo", "application/vnd.tmobile-livetv"}, 515 | {"tpt", "application/vnd.trid.tpt"}, 516 | {"mxs", "application/vnd.triscape.mxs"}, 517 | {"tra", "application/vnd.trueapp"}, 518 | {"ufd", "application/vnd.ufdl"}, 519 | {"ufdl", "application/vnd.ufdl"}, 520 | {"utz", "application/vnd.uiq.theme"}, 521 | {"umj", "application/vnd.umajin"}, 522 | {"unityweb", "application/vnd.unity"}, 523 | {"uoml", "application/vnd.uoml+xml"}, 524 | {"vcx", "application/vnd.vcx"}, 525 | {"vsd", "application/vnd.visio"}, 526 | {"vst", "application/vnd.visio"}, 527 | {"vss", "application/vnd.visio"}, 528 | {"vsw", "application/vnd.visio"}, 529 | {"vis", "application/vnd.visionary"}, 530 | {"vsf", "application/vnd.vsf"}, 531 | {"wbxml", "application/vnd.wap.wbxml"}, 532 | {"wmlc", "application/vnd.wap.wmlc"}, 533 | {"wmlsc", "application/vnd.wap.wmlscriptc"}, 534 | {"wtb", "application/vnd.webturbo"}, 535 | {"nbp", "application/vnd.wolfram.player"}, 536 | {"wpd", "application/vnd.wordperfect"}, 537 | {"wqd", "application/vnd.wqd"}, 538 | {"stf", "application/vnd.wt.stf"}, 539 | {"xar", "application/vnd.xara"}, 540 | {"xfdl", "application/vnd.xfdl"}, 541 | {"hvd", "application/vnd.yamaha.hv-dic"}, 542 | {"hvs", "application/vnd.yamaha.hv-script"}, 543 | {"hvp", "application/vnd.yamaha.hv-voice"}, 544 | {"osf", "application/vnd.yamaha.openscoreformat"}, 545 | {"osfpvg", "application/vnd.yamaha.openscoreformat.osfpvg+xml"}, 546 | {"saf", "application/vnd.yamaha.smaf-audio"}, 547 | {"spf", "application/vnd.yamaha.smaf-phrase"}, 548 | {"cmp", "application/vnd.yellowriver-custom-menu"}, 549 | {"zir", "application/vnd.zul"}, 550 | {"zirz", "application/vnd.zul"}, 551 | {"zaz", "application/vnd.zzazz.deck+xml"}, 552 | {"vxml", "application/voicexml+xml"}, 553 | {"wasm", "application/wasm"}, 554 | {"wgt", "application/widget"}, 555 | {"hlp", "application/winhlp"}, 556 | {"wsdl", "application/wsdl+xml"}, 557 | {"wspolicy", "application/wspolicy+xml"}, 558 | {"7z", "application/x-7z-compressed"}, 559 | {"abw", "application/x-abiword"}, 560 | {"ace", "application/x-ace-compressed"}, 561 | {"dmg", "application/x-apple-diskimage"}, 562 | {"aab", "application/x-authorware-bin"}, 563 | {"x32", "application/x-authorware-bin"}, 564 | {"u32", "application/x-authorware-bin"}, 565 | {"vox", "application/x-authorware-bin"}, 566 | {"aam", "application/x-authorware-map"}, 567 | {"aas", "application/x-authorware-seg"}, 568 | {"bcpio", "application/x-bcpio"}, 569 | {"torrent", "application/x-bittorrent"}, 570 | {"blb", "application/x-blorb"}, 571 | {"blorb", "application/x-blorb"}, 572 | {"bz", "application/x-bzip"}, 573 | {"bz2", "application/x-bzip2"}, 574 | {"boz", "application/x-bzip2"}, 575 | {"cbr", "application/x-cbr"}, 576 | {"cba", "application/x-cbr"}, 577 | {"cbt", "application/x-cbr"}, 578 | {"cbz", "application/x-cbr"}, 579 | {"cb7", "application/x-cbr"}, 580 | {"vcd", "application/x-cdlink"}, 581 | {"cfs", "application/x-cfs-compressed"}, 582 | {"chat", "application/x-chat"}, 583 | {"pgn", "application/x-chess-pgn"}, 584 | {"nsc", "application/x-conference"}, 585 | {"cpio", "application/x-cpio"}, 586 | {"csh", "application/x-csh"}, 587 | {"deb", "application/x-debian-package"}, 588 | {"udeb", "application/x-debian-package"}, 589 | {"dgc", "application/x-dgc-compressed"}, 590 | {"dir", "application/x-director"}, 591 | {"dcr", "application/x-director"}, 592 | {"dxr", "application/x-director"}, 593 | {"cst", "application/x-director"}, 594 | {"cct", "application/x-director"}, 595 | {"cxt", "application/x-director"}, 596 | {"w3d", "application/x-director"}, 597 | {"fgd", "application/x-director"}, 598 | {"swa", "application/x-director"}, 599 | {"wad", "application/x-doom"}, 600 | {"ncx", "application/x-dtbncx+xml"}, 601 | {"dtb", "application/x-dtbook+xml"}, 602 | {"res", "application/x-dtbresource+xml"}, 603 | {"dvi", "application/x-dvi"}, 604 | {"evy", "application/x-envoy"}, 605 | {"eva", "application/x-eva"}, 606 | {"bdf", "application/x-font-bdf"}, 607 | {"gsf", "application/x-font-ghostscript"}, 608 | {"psf", "application/x-font-linux-psf"}, 609 | {"pcf", "application/x-font-pcf"}, 610 | {"snf", "application/x-font-snf"}, 611 | {"pfa", "application/x-font-type1"}, 612 | {"pfb", "application/x-font-type1"}, 613 | {"pfm", "application/x-font-type1"}, 614 | {"afm", "application/x-font-type1"}, 615 | {"arc", "application/x-freearc"}, 616 | {"spl", "application/x-futuresplash"}, 617 | {"gca", "application/x-gca-compressed"}, 618 | {"ulx", "application/x-glulx"}, 619 | {"gnumeric", "application/x-gnumeric"}, 620 | {"gramps", "application/x-gramps-xml"}, 621 | {"gtar", "application/x-gtar"}, 622 | {"hdf", "application/x-hdf"}, 623 | {"install", "application/x-install-instructions"}, 624 | {"iso", "application/x-iso9660-image"}, 625 | {"jnlp", "application/x-java-jnlp-file"}, 626 | {"latex", "application/x-latex"}, 627 | {"lzh", "application/x-lzh-compressed"}, 628 | {"lha", "application/x-lzh-compressed"}, 629 | {"mie", "application/x-mie"}, 630 | {"prc", "application/x-mobipocket-ebook"}, 631 | {"mobi", "application/x-mobipocket-ebook"}, 632 | {"application", "application/x-ms-application"}, 633 | {"lnk", "application/x-ms-shortcut"}, 634 | {"wmd", "application/x-ms-wmd"}, 635 | {"wmz", "application/x-msmetafile"}, 636 | {"xbap", "application/x-ms-xbap"}, 637 | {"mdb", "application/x-msaccess"}, 638 | {"obd", "application/x-msbinder"}, 639 | {"crd", "application/x-mscardfile"}, 640 | {"clp", "application/x-msclip"}, 641 | {"exe", "application/x-msdownload"}, 642 | {"dll", "application/x-msdownload"}, 643 | {"com", "application/x-msdownload"}, 644 | {"bat", "application/x-msdownload"}, 645 | {"msi", "application/x-msdownload"}, 646 | {"mvb", "application/x-msmediaview"}, 647 | {"m13", "application/x-msmediaview"}, 648 | {"m14", "application/x-msmediaview"}, 649 | {"wmf", "application/x-msmetafile"}, 650 | {"emf", "application/x-msmetafile"}, 651 | {"emz", "application/x-msmetafile"}, 652 | {"mny", "application/x-msmoney"}, 653 | {"pub", "application/x-mspublisher"}, 654 | {"scd", "application/x-msschedule"}, 655 | {"trm", "application/x-msterminal"}, 656 | {"wri", "application/x-mswrite"}, 657 | {"nc", "application/x-netcdf"}, 658 | {"cdf", "application/x-netcdf"}, 659 | {"nzb", "application/x-nzb"}, 660 | {"p12", "application/x-pkcs12"}, 661 | {"pfx", "application/x-pkcs12"}, 662 | {"p7b", "application/x-pkcs7-certificates"}, 663 | {"spc", "application/x-pkcs7-certificates"}, 664 | {"p7r", "application/x-pkcs7-certreqresp"}, 665 | {"rar", "application/x-rar-compressed"}, 666 | {"ris", "application/x-research-info-systems"}, 667 | {"sh", "application/x-sh"}, 668 | {"shar", "application/x-shar"}, 669 | {"swf", "application/x-shockwave-flash"}, 670 | {"xap", "application/x-silverlight-app"}, 671 | {"sql", "application/x-sql"}, 672 | {"sit", "application/x-stuffit"}, 673 | {"sitx", "application/x-stuffitx"}, 674 | {"srt", "application/x-subrip"}, 675 | {"sv4cpio", "application/x-sv4cpio"}, 676 | {"sv4crc", "application/x-sv4crc"}, 677 | {"t3", "application/x-t3vm-image"}, 678 | {"gam", "application/x-tads"}, 679 | {"tar", "application/x-tar"}, 680 | {"tcl", "application/x-tcl"}, 681 | {"tex", "application/x-tex"}, 682 | {"tfm", "application/x-tex-tfm"}, 683 | {"texinfo", "application/x-texinfo"}, 684 | {"texi", "application/x-texinfo"}, 685 | {"obj", "application/x-tgif"}, 686 | {"ustar", "application/x-ustar"}, 687 | {"src", "application/x-wais-source"}, 688 | {"der", "application/x-x509-ca-cert"}, 689 | {"crt", "application/x-x509-ca-cert"}, 690 | {"fig", "application/x-xfig"}, 691 | {"xlf", "application/x-xliff+xml"}, 692 | {"xpi", "application/x-xpinstall"}, 693 | {"xz", "application/x-xz"}, 694 | {"z1", "application/x-zmachine"}, 695 | {"z2", "application/x-zmachine"}, 696 | {"z3", "application/x-zmachine"}, 697 | {"z4", "application/x-zmachine"}, 698 | {"z5", "application/x-zmachine"}, 699 | {"z6", "application/x-zmachine"}, 700 | {"z7", "application/x-zmachine"}, 701 | {"z8", "application/x-zmachine"}, 702 | {"xaml", "application/xaml+xml"}, 703 | {"xdf", "application/xcap-diff+xml"}, 704 | {"xenc", "application/xenc+xml"}, 705 | {"xhtml", "application/xhtml+xml"}, 706 | {"xht", "application/xhtml+xml"}, 707 | {"xml", "application/xml"}, 708 | {"xsl", "application/xml"}, 709 | {"dtd", "application/xml-dtd"}, 710 | {"xop", "application/xop+xml"}, 711 | {"xpl", "application/xproc+xml"}, 712 | {"xslt", "application/xslt+xml"}, 713 | {"xspf", "application/xspf+xml"}, 714 | {"mxml", "application/xv+xml"}, 715 | {"xhvml", "application/xv+xml"}, 716 | {"xvml", "application/xv+xml"}, 717 | {"xvm", "application/xv+xml"}, 718 | {"yang", "application/yang"}, 719 | {"yin", "application/yin+xml"}, 720 | {"zip", "application/zip"}, 721 | {"adp", "audio/adpcm"}, 722 | {"au", "audio/basic"}, 723 | {"snd", "audio/basic"}, 724 | {"mid", "audio/midi"}, 725 | {"midi", "audio/midi"}, 726 | {"kar", "audio/midi"}, 727 | {"rmi", "audio/midi"}, 728 | {"m4a", "audio/mp4"}, 729 | {"mp4a", "audio/mp4"}, 730 | {"mpga", "audio/mpeg"}, 731 | {"mp2", "audio/mpeg"}, 732 | {"mp2a", "audio/mpeg"}, 733 | {"mp3", "audio/mpeg"}, 734 | {"m2a", "audio/mpeg"}, 735 | {"m3a", "audio/mpeg"}, 736 | {"oga", "audio/ogg"}, 737 | {"ogg", "audio/ogg"}, 738 | {"spx", "audio/ogg"}, 739 | {"opus", "audio/ogg"}, 740 | {"s3m", "audio/s3m"}, 741 | {"sil", "audio/silk"}, 742 | {"uva", "audio/vnd.dece.audio"}, 743 | {"uvva", "audio/vnd.dece.audio"}, 744 | {"eol", "audio/vnd.digital-winds"}, 745 | {"dra", "audio/vnd.dra"}, 746 | {"dts", "audio/vnd.dts"}, 747 | {"dtshd", "audio/vnd.dts.hd"}, 748 | {"lvp", "audio/vnd.lucent.voice"}, 749 | {"pya", "audio/vnd.ms-playready.media.pya"}, 750 | {"ecelp4800", "audio/vnd.nuera.ecelp4800"}, 751 | {"ecelp7470", "audio/vnd.nuera.ecelp7470"}, 752 | {"ecelp9600", "audio/vnd.nuera.ecelp9600"}, 753 | {"rip", "audio/vnd.rip"}, 754 | {"weba", "audio/webm"}, 755 | {"aac", "audio/x-aac"}, 756 | {"aif", "audio/x-aiff"}, 757 | {"aiff", "audio/x-aiff"}, 758 | {"aifc", "audio/x-aiff"}, 759 | {"caf", "audio/x-caf"}, 760 | {"flac", "audio/x-flac"}, 761 | {"mka", "audio/x-matroska"}, 762 | {"m3u", "audio/x-mpegurl"}, 763 | {"wax", "audio/x-ms-wax"}, 764 | {"wma", "audio/x-ms-wma"}, 765 | {"ram", "audio/x-pn-realaudio"}, 766 | {"ra", "audio/x-pn-realaudio"}, 767 | {"rmp", "audio/x-pn-realaudio-plugin"}, 768 | {"wav", "audio/x-wav"}, 769 | {"xm", "audio/xm"}, 770 | {"cdx", "chemical/x-cdx"}, 771 | {"cif", "chemical/x-cif"}, 772 | {"cmdf", "chemical/x-cmdf"}, 773 | {"cml", "chemical/x-cml"}, 774 | {"csml", "chemical/x-csml"}, 775 | {"xyz", "chemical/x-xyz"}, 776 | {"ttc", "font/collection"}, 777 | {"otf", "font/otf"}, 778 | {"ttf", "font/ttf"}, 779 | {"woff", "font/woff"}, 780 | {"woff2", "font/woff2"}, 781 | {"avif", "image/avif"}, 782 | {"bmp", "image/bmp"}, 783 | {"cgm", "image/cgm"}, 784 | {"g3", "image/g3fax"}, 785 | {"gif", "image/gif"}, 786 | {"ief", "image/ief"}, 787 | {"jpeg", "image/jpeg"}, 788 | {"jpg", "image/jpeg"}, 789 | {"jpe", "image/jpeg"}, 790 | {"jxl", "image/jxl"}, 791 | {"ktx", "image/ktx"}, 792 | {"png", "image/png"}, 793 | {"btif", "image/prs.btif"}, 794 | {"sgi", "image/sgi"}, 795 | {"svg", "image/svg+xml"}, 796 | {"svgz", "image/svg+xml"}, 797 | {"tiff", "image/tiff"}, 798 | {"tif", "image/tiff"}, 799 | {"psd", "image/vnd.adobe.photoshop"}, 800 | {"uvi", "image/vnd.dece.graphic"}, 801 | {"uvvi", "image/vnd.dece.graphic"}, 802 | {"uvg", "image/vnd.dece.graphic"}, 803 | {"uvvg", "image/vnd.dece.graphic"}, 804 | {"djvu", "image/vnd.djvu"}, 805 | {"djv", "image/vnd.djvu"}, 806 | {"sub", "text/vnd.dvb.subtitle"}, 807 | {"dwg", "image/vnd.dwg"}, 808 | {"dxf", "image/vnd.dxf"}, 809 | {"fbs", "image/vnd.fastbidsheet"}, 810 | {"fpx", "image/vnd.fpx"}, 811 | {"fst", "image/vnd.fst"}, 812 | {"mmr", "image/vnd.fujixerox.edmics-mmr"}, 813 | {"rlc", "image/vnd.fujixerox.edmics-rlc"}, 814 | {"mdi", "image/vnd.ms-modi"}, 815 | {"wdp", "image/vnd.ms-photo"}, 816 | {"npx", "image/vnd.net-fpx"}, 817 | {"wbmp", "image/vnd.wap.wbmp"}, 818 | {"xif", "image/vnd.xiff"}, 819 | {"webp", "image/webp"}, 820 | {"3ds", "image/x-3ds"}, 821 | {"ras", "image/x-cmu-raster"}, 822 | {"cmx", "image/x-cmx"}, 823 | {"fh", "image/x-freehand"}, 824 | {"fhc", "image/x-freehand"}, 825 | {"fh4", "image/x-freehand"}, 826 | {"fh5", "image/x-freehand"}, 827 | {"fh7", "image/x-freehand"}, 828 | {"ico", "image/x-icon"}, 829 | {"sid", "image/x-mrsid-image"}, 830 | {"pcx", "image/x-pcx"}, 831 | {"pic", "image/x-pict"}, 832 | {"pct", "image/x-pict"}, 833 | {"pnm", "image/x-portable-anymap"}, 834 | {"pbm", "image/x-portable-bitmap"}, 835 | {"pgm", "image/x-portable-graymap"}, 836 | {"ppm", "image/x-portable-pixmap"}, 837 | {"rgb", "image/x-rgb"}, 838 | {"tga", "image/x-tga"}, 839 | {"xbm", "image/x-xbitmap"}, 840 | {"xpm", "image/x-xpixmap"}, 841 | {"xwd", "image/x-xwindowdump"}, 842 | {"eml", "message/rfc822"}, 843 | {"mime", "message/rfc822"}, 844 | {"igs", "model/iges"}, 845 | {"iges", "model/iges"}, 846 | {"msh", "model/mesh"}, 847 | {"mesh", "model/mesh"}, 848 | {"silo", "model/mesh"}, 849 | {"dae", "model/vnd.collada+xml"}, 850 | {"dwf", "model/vnd.dwf"}, 851 | {"gdl", "model/vnd.gdl"}, 852 | {"gtw", "model/vnd.gtw"}, 853 | {"vtu", "model/vnd.vtu"}, 854 | {"wrl", "model/vrml"}, 855 | {"vrml", "model/vrml"}, 856 | {"x3db", "model/x3d+binary"}, 857 | {"x3dbz", "model/x3d+binary"}, 858 | {"x3dv", "model/x3d+vrml"}, 859 | {"x3dvz", "model/x3d+vrml"}, 860 | {"x3d", "model/x3d+xml"}, 861 | {"x3dz", "model/x3d+xml"}, 862 | {"appcache", "text/cache-manifest"}, 863 | {"ics", "text/calendar"}, 864 | {"ifb", "text/calendar"}, 865 | {"css", "text/css"}, 866 | {"csv", "text/csv"}, 867 | {"html", "text/html"}, 868 | {"htm", "text/html"}, 869 | {"js", "text/javascript"}, 870 | {"mjs", "text/javascript"}, 871 | {"n3", "text/n3"}, 872 | {"txt", "text/plain"}, 873 | {"text", "text/plain"}, 874 | {"conf", "text/plain"}, 875 | {"def", "text/plain"}, 876 | {"list", "text/plain"}, 877 | {"log", "text/plain"}, 878 | {"in", "text/plain"}, 879 | {"dsc", "text/prs.lines.tag"}, 880 | {"rtx", "text/richtext"}, 881 | {"sgml", "text/sgml"}, 882 | {"sgm", "text/sgml"}, 883 | {"tsv", "text/tab-separated-values"}, 884 | {"t", "text/troff"}, 885 | {"tr", "text/troff"}, 886 | {"roff", "text/troff"}, 887 | {"man", "text/troff"}, 888 | {"me", "text/troff"}, 889 | {"ms", "text/troff"}, 890 | {"ttl", "text/turtle"}, 891 | {"uri", "text/uri-list"}, 892 | {"uris", "text/uri-list"}, 893 | {"urls", "text/uri-list"}, 894 | {"vcard", "text/vcard"}, 895 | {"curl", "text/vnd.curl"}, 896 | {"dcurl", "text/vnd.curl.dcurl"}, 897 | {"mcurl", "text/vnd.curl.mcurl"}, 898 | {"scurl", "text/vnd.curl.scurl"}, 899 | {"fly", "text/vnd.fly"}, 900 | {"flx", "text/vnd.fmi.flexstor"}, 901 | {"gv", "text/vnd.graphviz"}, 902 | {"3dml", "text/vnd.in3d.3dml"}, 903 | {"spot", "text/vnd.in3d.spot"}, 904 | {"jad", "text/vnd.sun.j2me.app-descriptor"}, 905 | {"wml", "text/vnd.wap.wml"}, 906 | {"wmls", "text/vnd.wap.wmlscript"}, 907 | {"s", "text/x-asm"}, 908 | {"asm", "text/x-asm"}, 909 | {"c", "text/x-c"}, 910 | {"cc", "text/x-c"}, 911 | {"cxx", "text/x-c"}, 912 | {"cpp", "text/x-c"}, 913 | {"h", "text/x-c"}, 914 | {"hh", "text/x-c"}, 915 | {"dic", "text/x-c"}, 916 | {"f", "text/x-fortran"}, 917 | {"for", "text/x-fortran"}, 918 | {"f77", "text/x-fortran"}, 919 | {"f90", "text/x-fortran"}, 920 | {"java", "text/x-java-source"}, 921 | {"nfo", "text/x-nfo"}, 922 | {"opml", "text/x-opml"}, 923 | {"p", "text/x-pascal"}, 924 | {"pas", "text/x-pascal"}, 925 | {"etx", "text/x-setext"}, 926 | {"sfv", "text/x-sfv"}, 927 | {"uu", "text/x-uuencode"}, 928 | {"vcs", "text/x-vcalendar"}, 929 | {"vcf", "text/x-vcard"}, 930 | {"3gp", "video/3gpp"}, 931 | {"3g2", "video/3gpp2"}, 932 | {"h261", "video/h261"}, 933 | {"h263", "video/h263"}, 934 | {"h264", "video/h264"}, 935 | {"jpgv", "video/jpeg"}, 936 | {"jpm", "video/jpm"}, 937 | {"jpgm", "video/jpm"}, 938 | {"mj2", "video/mj2"}, 939 | {"mjp2", "video/mj2"}, 940 | {"ts", "video/mp2t"}, 941 | {"m2t", "video/mp2t"}, 942 | {"m2ts", "video/mp2t"}, 943 | {"mts", "video/mp2t"}, 944 | {"mp4", "video/mp4"}, 945 | {"mp4v", "video/mp4"}, 946 | {"mpg4", "video/mp4"}, 947 | {"mpeg", "video/mpeg"}, 948 | {"mpg", "video/mpeg"}, 949 | {"mpe", "video/mpeg"}, 950 | {"m1v", "video/mpeg"}, 951 | {"m2v", "video/mpeg"}, 952 | {"ogv", "video/ogg"}, 953 | {"qt", "video/quicktime"}, 954 | {"mov", "video/quicktime"}, 955 | {"uvh", "video/vnd.dece.hd"}, 956 | {"uvvh", "video/vnd.dece.hd"}, 957 | {"uvm", "video/vnd.dece.mobile"}, 958 | {"uvvm", "video/vnd.dece.mobile"}, 959 | {"uvp", "video/vnd.dece.pd"}, 960 | {"uvvp", "video/vnd.dece.pd"}, 961 | {"uvs", "video/vnd.dece.sd"}, 962 | {"uvvs", "video/vnd.dece.sd"}, 963 | {"uvv", "video/vnd.dece.video"}, 964 | {"uvvv", "video/vnd.dece.video"}, 965 | {"dvb", "video/vnd.dvb.file"}, 966 | {"fvt", "video/vnd.fvt"}, 967 | {"mxu", "video/vnd.mpegurl"}, 968 | {"m4u", "video/vnd.mpegurl"}, 969 | {"pyv", "video/vnd.ms-playready.media.pyv"}, 970 | {"uvu", "video/vnd.uvvu.mp4"}, 971 | {"uvvu", "video/vnd.uvvu.mp4"}, 972 | {"viv", "video/vnd.vivo"}, 973 | {"webm", "video/webm"}, 974 | {"f4v", "video/x-f4v"}, 975 | {"fli", "video/x-fli"}, 976 | {"flv", "video/x-flv"}, 977 | {"m4v", "video/x-m4v"}, 978 | {"mkv", "video/x-matroska"}, 979 | {"mk3d", "video/x-matroska"}, 980 | {"mks", "video/x-matroska"}, 981 | {"mng", "video/x-mng"}, 982 | {"asf", "video/x-ms-asf"}, 983 | {"asx", "video/x-ms-asf"}, 984 | {"vob", "video/x-ms-vob"}, 985 | {"wm", "video/x-ms-wm"}, 986 | {"wmv", "video/x-ms-wmv"}, 987 | {"wmx", "video/x-ms-wmx"}, 988 | {"wvx", "video/x-ms-wvx"}, 989 | {"avi", "video/x-msvideo"}, 990 | {"movie", "video/x-sgi-movie"}, 991 | {"smv", "video/x-smv"}, 992 | {"ice", "x-conference/x-cooltalk"}, 993 | -------------------------------------------------------------------------------- /src/Resource.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | httpserver 3 | Resource.cpp 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #include "Resource.h" 20 | 21 | #include 22 | 23 | Resource::Resource(std::string const& loc, bool dir) : location(loc), directory(dir) { 24 | } 25 | 26 | Resource::~Resource() { 27 | if (data != nullptr) { 28 | delete [] data; 29 | data = nullptr; 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/Resource.h: -------------------------------------------------------------------------------- 1 | /** 2 | httpserver 3 | Resource.h 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #ifndef _RESOURCE_H_ 20 | #define _RESOURCE_H_ 21 | 22 | #include 23 | 24 | class Resource { 25 | 26 | private: 27 | uint8_t* data = nullptr; // File data 28 | uint32_t size = 0; 29 | std::string mimeType = ""; 30 | std::string location; // Disk path location within the server 31 | bool directory; 32 | 33 | public: 34 | Resource(std::string const& loc, bool dir = false); 35 | ~Resource(); 36 | 37 | // Setters 38 | 39 | void setData(uint8_t* d, uint32_t s) { 40 | data = d; 41 | size = s; 42 | } 43 | 44 | void setMimeType(std::string_view mt) { 45 | mimeType = mt; 46 | } 47 | 48 | // Getters 49 | 50 | std::string getMimeType() const { 51 | return mimeType; 52 | } 53 | 54 | std::string getLocation() const { 55 | return location; 56 | } 57 | 58 | bool isDirectory() const { 59 | return directory; 60 | } 61 | 62 | uint8_t* getData() const { 63 | return data; 64 | } 65 | 66 | uint32_t getSize() const { 67 | return size; 68 | } 69 | 70 | // Get the file name 71 | std::string getName() const { 72 | std::string name = ""; 73 | if (auto slash_pos = location.find_last_of("/"); slash_pos != std::string::npos) 74 | name = location.substr(slash_pos + 1); 75 | return name; 76 | } 77 | 78 | // Get the file extension 79 | std::string getExtension() const { 80 | std::string ext = ""; 81 | if (auto ext_pos = location.find_last_of("."); ext_pos != std::string::npos) 82 | ext = location.substr(ext_pos + 1); 83 | return ext; 84 | } 85 | }; 86 | 87 | #endif 88 | -------------------------------------------------------------------------------- /src/ResourceHost.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | httpserver 3 | ResourceHost.cpp 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #include "ResourceHost.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | // Valid files to serve as an index of a directory 30 | const static std::vector g_validIndexes = { 31 | "index.html", 32 | "index.htm" 33 | }; 34 | 35 | // Dictionary that relates file extensions to their MIME type 36 | const static std::unordered_map, std::equal_to<>> g_mimeMap = { 37 | #include "MimeTypes.inc" 38 | }; 39 | 40 | ResourceHost::ResourceHost(std::string const& base) : baseDiskPath(base) { 41 | // TODO: Check to see if the baseDiskPath is a valid path 42 | } 43 | 44 | /** 45 | * Looks up a MIME type in the dictionary 46 | * 47 | * @param ext File extension to use for the lookup 48 | * @return MIME type as a String. If type could not be found, returns an empty string 49 | */ 50 | std::string ResourceHost::lookupMimeType(std::string const& ext) const { 51 | auto it = g_mimeMap.find(ext); 52 | if (it == g_mimeMap.end()) 53 | return ""; 54 | 55 | return it->second; 56 | } 57 | 58 | /** 59 | * Read File 60 | * Read a file from disk and return the appropriate Resource object 61 | * This creates a new Resource object - callers are expected to dispose of the return value if non-NULL 62 | * 63 | * @param path Full disk path of the file 64 | * @param sb Filled in stat struct 65 | * @return Return's the resource object upon successful load 66 | */ 67 | std::unique_ptr ResourceHost::readFile(std::string const& path, struct stat const& sb) { 68 | // Make sure the webserver USER owns the file 69 | if (!(sb.st_mode & S_IRWXU)) 70 | return nullptr; 71 | 72 | // Create a new Resource object and setup it's contents 73 | auto res = std::make_unique(path); 74 | std::string name = res->getName(); 75 | if (name.length() == 0) { 76 | return nullptr; // Malformed name 77 | } 78 | 79 | // Always disallow hidden files 80 | if (name.starts_with(".")) { 81 | return nullptr; 82 | } 83 | 84 | std::ifstream file; 85 | uint32_t len = 0; 86 | 87 | // Open the file 88 | file.open(path, std::ios::binary); 89 | 90 | // Return null if the file failed to open 91 | if (!file.is_open()) 92 | return nullptr; 93 | 94 | // Get the length of the file 95 | len = sb.st_size; 96 | 97 | // Allocate memory for contents of file and read in the contents 98 | auto fdata = new uint8_t[len]; 99 | memset(fdata, 0x00, len); 100 | file.read((char*)fdata, len); 101 | 102 | // Close the file 103 | file.close(); 104 | 105 | if (auto mimetype = lookupMimeType(res->getExtension()); mimetype.length() != 0) { 106 | res->setMimeType(mimetype); 107 | } else { 108 | res->setMimeType("application/octet-stream"); // default to binary 109 | } 110 | 111 | res->setData(fdata, len); 112 | 113 | return res; 114 | } 115 | 116 | /** 117 | * Read Directory 118 | * Read a directory (list or index) from disk into a Resource object 119 | * This creates a new Resource object - callers are expected to dispose of the return value if non-NULL 120 | * 121 | * @param path Full disk path of the file 122 | * @param sb Filled in stat struct 123 | * @return Return's the resource object upon successful load 124 | */ 125 | std::unique_ptr ResourceHost::readDirectory(std::string path, struct stat const& sb) { 126 | // Make the path end with a / (for consistency) if it doesnt already 127 | if (path.empty() || path[path.length() - 1] != '/') 128 | path += "/"; 129 | 130 | // Probe for valid indexes 131 | uint32_t numIndexes = std::size(g_validIndexes); 132 | std::string loadIndex; 133 | struct stat sidx = {0}; 134 | for (uint32_t i = 0; i < numIndexes; i++) { 135 | loadIndex = path + g_validIndexes[i]; 136 | // Found a suitable index file to load and return to the client 137 | if (stat(loadIndex.c_str(), &sidx) == 0) 138 | return readFile(loadIndex, sidx); 139 | } 140 | 141 | // Make sure the webserver USER owns the directory 142 | if (!(sb.st_mode & S_IRWXU)) 143 | return nullptr; 144 | 145 | // Generate an HTML directory listing 146 | std::string listing = generateDirList(path); 147 | 148 | uint32_t slen = listing.length(); 149 | auto sdata = new uint8_t[slen]; 150 | memset(sdata, 0x00, slen); 151 | strncpy((char*)sdata, listing.c_str(), slen); 152 | 153 | auto res = std::make_unique(path, true); 154 | res->setMimeType("text/html"); 155 | res->setData(sdata, slen); 156 | 157 | return res; 158 | } 159 | 160 | /** 161 | * Return an HTML directory listing provided by the relative path dirPath 162 | * 163 | * @param path Full disk path of the file 164 | * @return HTML string representation of the directory. Blank string if invalid directory 165 | */ 166 | std::string ResourceHost::generateDirList(std::string const& path) const { 167 | // Get just the relative uri from the entire path by stripping out the baseDiskPath from the beginning 168 | size_t uri_pos = path.find(baseDiskPath); 169 | std::string uri = "?"; 170 | if (uri_pos != std::string::npos) 171 | uri = path.substr(uri_pos + baseDiskPath.length()); 172 | 173 | std::stringstream ret; 174 | ret << "" << uri << ""; 175 | 176 | const struct dirent* ent = nullptr; 177 | DIR* dir = opendir(path.c_str()); 178 | if (dir == nullptr) 179 | return ""; 180 | 181 | // Page title, displaying the URI of the directory being listed 182 | ret << "

Index of " << uri << "



"; 183 | 184 | // Add all files and directories to the return 185 | while ((ent = readdir(dir)) != nullptr) { 186 | // Skip any 'hidden' files (starting with a '.') 187 | if (ent->d_name[0] == '.') 188 | continue; 189 | 190 | // Display link to object in directory: 191 | ret << "d_name << "\">" << ent->d_name << "
"; 192 | } 193 | 194 | // Close the directory 195 | closedir(dir); 196 | 197 | ret << ""; 198 | 199 | return ret.str(); 200 | } 201 | 202 | /** 203 | * Retrieve a resource from the File system 204 | * This returns a new Resource object - callers are expected to dispose of the return value if non-NULL 205 | * 206 | * @param uri The URI sent in the request 207 | * @return NULL if unable to load the resource. Resource object 208 | */ 209 | std::unique_ptr ResourceHost::getResource(std::string const& uri) { 210 | if (uri.length() > 255 || uri.empty()) 211 | return nullptr; 212 | 213 | // Do not allow directory traversal 214 | if (uri.contains("../") || uri.contains("/..")) 215 | return nullptr; 216 | 217 | // Gather info about the resource with stat: determine if it's a directory or file, check if its owned by group/user, modify times 218 | std::string path = baseDiskPath + uri; 219 | struct stat sb = {0}; 220 | if (stat(path.c_str(), &sb) != 0) 221 | return nullptr; // File not found 222 | 223 | // Determine file type 224 | if (sb.st_mode & S_IFDIR) { // Directory 225 | // Read a directory list or index into memory from FS 226 | return readDirectory(path, sb); 227 | } else if (sb.st_mode & S_IFREG) { // Regular file 228 | // Attempt to load the file into memory from the FS 229 | return readFile(path, sb); 230 | } else { 231 | // Something else..device, socket, symlink 232 | } 233 | 234 | return nullptr; 235 | } 236 | -------------------------------------------------------------------------------- /src/ResourceHost.h: -------------------------------------------------------------------------------- 1 | /** 2 | httpserver 3 | ResourceHost.h 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #ifndef _RESOURCEHOST_H_ 20 | #define _RESOURCEHOST_H_ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "Resource.h" 28 | 29 | class ResourceHost { 30 | private: 31 | // Local file system base path 32 | std::string baseDiskPath; 33 | 34 | private: 35 | // Returns a MIME type string given an extension 36 | std::string lookupMimeType(std::string const& ext) const; 37 | 38 | // Read a file from the FS and into a Resource object 39 | std::unique_ptr readFile(std::string const& path, struct stat const& sb); 40 | 41 | // Reads a directory list or index from FS into a Resource object 42 | std::unique_ptr readDirectory(std::string path, struct stat const& sb); 43 | 44 | // Provide a string rep of the directory listing based on URI 45 | std::string generateDirList(std::string const& dirPath) const; 46 | 47 | public: 48 | explicit ResourceHost(std::string const& base); 49 | ~ResourceHost() = default; 50 | 51 | // Returns a Resource based on URI 52 | std::unique_ptr getResource(std::string const& uri); 53 | }; 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/SendQueueItem.h: -------------------------------------------------------------------------------- 1 | /** 2 | httpserver 3 | SendQueueItem.h 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #ifndef _SENDQUEUEITEM_H_ 20 | #define _SENDQUEUEITEM_H_ 21 | 22 | #include 23 | #include 24 | 25 | /** 26 | * SendQueueItem 27 | * Object represents a piece of data in a clients send queue 28 | * Contains a pointer to the send buffer and tracks the current amount of data sent (by offset) 29 | */ 30 | class SendQueueItem { 31 | 32 | private: 33 | std::unique_ptr sendData; 34 | uint32_t sendSize; 35 | uint32_t sendOffset = 0; 36 | bool disconnect; // Flag indicating if the client should be disconnected after this item is dequeued 37 | 38 | public: 39 | SendQueueItem(std::unique_ptr data, uint32_t size, bool dc) : sendData(std::move(data)), sendSize(size), disconnect(dc) { 40 | } 41 | 42 | ~SendQueueItem() = default; 43 | SendQueueItem(SendQueueItem const&) = delete; // Copy constructor 44 | SendQueueItem& operator=(SendQueueItem const&) = delete; // Copy assignment 45 | SendQueueItem(SendQueueItem &&) = delete; // Move 46 | SendQueueItem& operator=(SendQueueItem &&) = delete; // Move assignment 47 | 48 | void setOffset(uint32_t off) { 49 | sendOffset = off; 50 | } 51 | 52 | uint8_t* getRawDataPointer() const { 53 | return sendData.get(); 54 | } 55 | 56 | uint32_t getSize() const { 57 | return sendSize; 58 | } 59 | 60 | bool getDisconnect() const { 61 | return disconnect; 62 | } 63 | 64 | uint32_t getOffset() const { 65 | return sendOffset; 66 | } 67 | 68 | }; 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /src/convert_mimetypes.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import argparse 4 | 5 | def main(): 6 | parser = argparse.ArgumentParser(description="Convert Apache's mime.types file to our MimeTypes.inc") 7 | parser.add_argument('-s', '--source', required=True, type=str, help='Path to source mime.types file') 8 | parser.add_argument('-o', '--output', required=True, type=str, help='Path to target MimeTypes.inc file to overwrite') 9 | 10 | args = parser.parse_args() 11 | if not os.path.isfile(args.source): 12 | print("Source mime.types file must exist") 13 | parser.print_help() 14 | return 1 15 | 16 | mapping = {} 17 | 18 | with open(args.source, "r") as fh: 19 | for line in fh.readlines(): 20 | if line.startswith("#"): 21 | continue 22 | line = line.strip() 23 | parts = line.split() 24 | mimetype = parts[0].strip() 25 | exts = parts[1:] 26 | if not exts: 27 | print(f"No extensions for {mimetype}, skipping") 28 | continue 29 | 30 | for ext in exts: 31 | mapping.update({ext: mimetype}) 32 | 33 | with open(args.output, "w") as fh: 34 | for ext, mimetype in mapping.items(): 35 | fh.write(f'{{"{ext}", "{mimetype}"}},\n') 36 | 37 | return 0 38 | 39 | if __name__ == '__main__': 40 | sys.exit(main()) 41 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | httpserver 3 | main.cpp 4 | Copyright 2011-2025 Ramsey Kant 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "HTTPServer.h" 26 | #include "ResourceHost.h" 27 | 28 | static std::unique_ptr svr; 29 | 30 | // Ignore signals with this function 31 | void handleSigPipe([[maybe_unused]] int32_t snum) { 32 | return; 33 | } 34 | 35 | // Termination signal handler (Ctrl-C) 36 | void handleTermSig([[maybe_unused]] int32_t snum) { 37 | svr->canRun = false; 38 | } 39 | 40 | int main() 41 | { 42 | // Parse config file 43 | std::map> config; 44 | std::fstream cfile; 45 | std::string line; 46 | std::string key; 47 | std::string val; 48 | int32_t epos = 0; 49 | int32_t drop_uid = 0; 50 | int32_t drop_gid = 0; 51 | cfile.open("server.config"); 52 | if (!cfile.is_open()) { 53 | std::cout << "Unable to open server.config file in working directory" << std::endl; 54 | return -1; 55 | } 56 | while (getline(cfile, line)) { 57 | // Skip empty lines or those beginning with a # 58 | if (line.length() == 0 || line.rfind("#", 0) == 0) 59 | continue; 60 | 61 | epos = line.find("="); 62 | key = line.substr(0, epos); 63 | val = line.substr(epos + 1, line.length()); 64 | config.try_emplace(key, val); 65 | } 66 | cfile.close(); 67 | 68 | // Validate at least vhost, port, and diskpath are present 69 | if (!config.contains("vhost") || !config.contains("port") || !config.contains("diskpath")) { 70 | std::cout << "vhost, port, and diskpath must be supplied in the config, at a minimum" << std::endl; 71 | return -1; 72 | } 73 | 74 | // Break vhost into a comma separated list (if there are multiple vhost aliases) 75 | std::vector vhosts; 76 | std::string vhost_alias_str = config["vhost"]; 77 | std::string delimiter = ","; 78 | std::string token; 79 | size_t pos = 0; 80 | do { 81 | pos = vhost_alias_str.find(delimiter); 82 | token = vhost_alias_str.substr(0, pos); 83 | vhosts.push_back(token); 84 | vhost_alias_str.erase(0, pos + delimiter.length()); 85 | } while (pos != std::string::npos); 86 | 87 | // Check for optional drop_uid, drop_gid. Ensure both are set 88 | if (config.contains("drop_uid") && config.contains("drop_gid")) { 89 | drop_uid = atoi(config["drop_uid"].c_str()); 90 | drop_gid = atoi(config["drop_gid"].c_str()); 91 | 92 | if (drop_uid <= 0 || drop_gid <= 0) { 93 | // Both must be set, otherwise set back to 0 so we dont use 94 | drop_uid = drop_gid = 0; 95 | } 96 | } 97 | 98 | // Ignore SIGPIPE "Broken pipe" signals when socket connections are broken. 99 | signal(SIGPIPE, handleSigPipe); 100 | 101 | // Register termination signals 102 | signal(SIGABRT, &handleTermSig); 103 | signal(SIGINT, &handleTermSig); 104 | signal(SIGTERM, &handleTermSig); 105 | 106 | // Instantiate and start the server 107 | svr = std::make_unique(vhosts, atoi(config["port"].c_str()), config["diskpath"], drop_uid, drop_gid); 108 | if (!svr->start()) { 109 | svr->stop(); 110 | return -1; 111 | } 112 | 113 | // Run main event loop 114 | svr->process(); 115 | 116 | // Stop the server 117 | svr->stop(); 118 | 119 | return 0; 120 | } 121 | --------------------------------------------------------------------------------