├── .gitignore ├── LICENSE ├── README.md ├── TODO ├── docs ├── Doxyfile └── pages │ ├── examples.dox │ └── main-page.dox ├── examples ├── bodydata │ ├── bodydata.cpp │ ├── bodydata.h │ └── bodydata.pro ├── examples.pro ├── greeting │ ├── greeting.cpp │ ├── greeting.h │ └── greeting.pro └── helloworld │ ├── helloworld.cpp │ ├── helloworld.h │ └── helloworld.pro ├── http-parser ├── .gitignore ├── .mailmap ├── .travis.yml ├── AUTHORS ├── LICENSE-MIT ├── README.md ├── bench.c ├── contrib │ ├── parsertrace.c │ └── url_parser.c ├── http_parser.c ├── http_parser.gyp ├── http_parser.h └── test.c ├── qhttpserver.pri ├── qhttpserver.pro └── src ├── qhttpconnection.cpp ├── qhttpconnection.h ├── qhttprequest.cpp ├── qhttprequest.h ├── qhttpresponse.cpp ├── qhttpresponse.h ├── qhttpserver.cpp ├── qhttpserver.h ├── qhttpserverapi.h ├── qhttpserverfwd.h └── src.pro /.gitignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | build 3 | lib 4 | 5 | # Generated 6 | Makefile 7 | *.o 8 | moc_* 9 | 10 | # Docs 11 | docs/html 12 | 13 | # Build folders 14 | */debug 15 | */release 16 | */*/debug 17 | */*/release 18 | 19 | # Visual studio 20 | *.suo 21 | *.ncb 22 | *.user 23 | *.pdb 24 | *.idb 25 | *.vcproj 26 | *.vcxproj 27 | *.vcxproj.filters 28 | *.lib 29 | *.sln 30 | *.rc 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2011-2014 Nikhil Marathe 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | QHttpServer is NOT actively maintained. [Amir Zamani](https://github.com/azadkuh)'s [qhttp](https://github.com/azadkuh/qhttp) fixes some of the bugs here and is generally a nicer place to start from. 2 | 3 | QHttpServer 4 | =========== 5 | 6 | A Qt HTTP Server - because hard-core programmers write web-apps in C++ :) 7 | 8 | It uses Joyent's [HTTP Parser](http://github.com/joyent/http-parser) and is asynchronous and does not require any inheritance. 9 | 10 | QHttpServer is available under the MIT License. 11 | 12 | **NOTE: QHttpServer is NOT fully HTTP compliant right now! DO NOT use it for 13 | anything complex** 14 | 15 | Installation 16 | ------------ 17 | 18 | Requires Qt 4 or Qt 5. 19 | 20 | qmake && make && su -c 'make install' 21 | 22 | To link to your projects put this in your project's qmake project file 23 | 24 | LIBS += -lqhttpserver 25 | 26 | By default, the installation prefix is /usr/local. To change that to /usr, 27 | for example, run: 28 | 29 | qmake -r PREFIX=/usr 30 | 31 | Usage 32 | ----- 33 | 34 | Include the headers 35 | 36 | #include 37 | #include 38 | #include 39 | 40 | Create a server, and connect to the signal for new requests 41 | 42 | QHttpServer *server = new QHttpServer; 43 | connect(server, SIGNAL(newRequest(QHttpRequest*, QHttpResponse*)), 44 | handler, SLOT(handle(QHttpRequest*, QHttpResponse*))); 45 | 46 | // let's go 47 | server->listen(8080); 48 | 49 | In the handler, you may dispatch on routes or do whatever other things 50 | you want. See the API documentation for what information 51 | is provided about the request via the QHttpRequest object. 52 | 53 | To send data back to the browser and end the request: 54 | 55 | void Handler::handle(QHttpRequest *req, QHttpResponse *resp) 56 | { 57 | resp->setHeader("Content-Length", 11); 58 | resp->writeHead(200); // everything is OK 59 | resp->write("Hello World"); 60 | resp->end(); 61 | } 62 | 63 | The server and request/response objects emit various signals 64 | and have guarantees about memory management. See the API documentation for 65 | these. 66 | 67 | Contribute 68 | ---------- 69 | 70 | Feel free to file issues, branch and send pull requests. If you plan to work on a major feature (say WebSocket support), please run it by me first by filing an issue! qhttpserver has a narrow scope and API and I'd like to keep it that way, so a thousand line patch that implements the kitchen sink is unlikely to be accepted. 71 | 72 | - Nikhil Marathe (maintainer) 73 | 74 | Everybody who has ever contributed shows up in [Contributors](https://github.com/nikhilm/qhttpserver/graphs/contributors). 75 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * Expect & Continue stuff 2 | * Chunked encoding support 3 | * Only copy over public headers etc. 4 | * connection object should connect to QHttpResponse::destroyed() 5 | and stop pushing data into it or whatever if the object is destroyed. 6 | * response object should keep track of emitting done() and not accept writes after that 7 | * handle encoding in response write and end 8 | -------------------------------------------------------------------------------- /docs/pages/examples.dox: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | @example helloworld/helloworld.cpp 4 | @example helloworld/helloworld.h 5 | @example greeting/greeting.cpp 6 | @example greeting/greeting.h 7 | @example bodydata/bodydata.cpp 8 | @example bodydata/bodydata.h 9 | 10 | */ -------------------------------------------------------------------------------- /docs/pages/main-page.dox: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | @mainpage %QHttpServer Documentation 4 | 5 | @section introduction Introduction 6 | 7 | %QHttpServer is a easy to use, fast and light-weight 8 | HTTP Server suitable for C++ web applications backed 9 | by Qt. Since C++ web applications are pretty uncommon 10 | the market for this project is pretty low. 11 | 12 | But integrating this with a module like QtScript and using it to write 13 | JavaScript web applications is a tempting possibility, and something that 14 | I demonstrated at conf.kde.in 2011. The slides 15 | can be found on Github. 16 | 17 | %QHttpServer uses a signal-slots based mechanism 18 | for all communication, so no inheritance is required. 19 | It tries to be as asynchronous as possible, to the 20 | extent that request body data is also delivered as and 21 | when it is received over the socket via signals. This 22 | kind of programming may take some getting used to. 23 | 24 | %QHttpServer is backed by Ryan 25 | Dahl's secure and fast http parser which makes it streaming 26 | till the lowest level. 27 | 28 | @section usage Usage 29 | 30 | Using %QHttpServer is very simple. Simply create a QHttpServer, 31 | connect a slot to the newRequest() signal and use the request and 32 | response objects. 33 | See the QHttpServer class documentation for an example. 34 | 35 | @section memorymanagement Memory Management 36 | 37 | The QHttpRequest and QHttpResponse deletion policies 38 | are such. 39 | 40 | QHttpRequest is never deleted by %QHttpServer. 41 | Since it is not possible to determine till what point the application 42 | may want access to its data, it is up to the application to delete it. 43 | A recommended way to handle this is to create a new responder object for 44 | every request and to delete the request in that object's destructor. The 45 | object itself can be deleted by connecting to QHttpResponse's done() 46 | slot as explained below. 47 | 48 | You should not delete the QHttpRequest object until it 49 | has emitted an QHttpRequest::end() signal. 50 | 51 | QHttpResponse queues itself up for auto-deletion once the application 52 | calls its end() method. Once the data has been flushed to the underlying 53 | socket, the object will emit a QHttpResponse::done() signal before queueing itself up 54 | for deletion. You should not interact with the response 55 | object once it has emitted QHttpResponse::done() although actual deletion does not 56 | happen until QHttpResponse::destroyed() is emitted. 57 | QHttpResponse::done() serves as a useful way to handle memory management of the 58 | application itself. For example: 59 | 60 | @code 61 | 62 | MyApp::MyApp() : QObject() 63 | { 64 | QHttpServer *server = new QHttpServer(this); 65 | connect(server, SIGNAL(newRequest(...)), this, SLOT(handle(...))); 66 | s.listen(8080); 67 | } 68 | 69 | void MyApp::handle(QHttpRequest *request, QHttpResponse *response) 70 | { 71 | if (request->path() == x) // Match a route 72 | new Responder(request, response); 73 | else 74 | new PageNotFound(request, response); 75 | } 76 | 77 | ... 78 | 79 | Responder::Responder(QHttpRequest *request, QHttpResponse *response) 80 | { 81 | m_request = request; 82 | 83 | connect(request, SIGNAL(end()), response, SLOT(end())); 84 | 85 | // Once the request is complete, the response is sent. 86 | // When the response ends, it deletes itself 87 | // the Responder object connects to done() 88 | // which will lead to it being deleted 89 | // and this will delete the request. 90 | // So all 3 are properly deleted. 91 | connect(response, SIGNAL(done()), this, SLOT(deleteLater())); 92 | 93 | response->writeHead(200); 94 | response->write("Quitting soon"); 95 | } 96 | 97 | Responder::~Responder() 98 | { 99 | delete m_request; 100 | m_request = 0; 101 | } 102 | 103 | @endcode 104 | 105 | */ -------------------------------------------------------------------------------- /examples/bodydata/bodydata.cpp: -------------------------------------------------------------------------------- 1 | #include "bodydata.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | /// BodyData 13 | 14 | BodyData::BodyData() 15 | { 16 | QHttpServer *server = new QHttpServer(this); 17 | connect(server, SIGNAL(newRequest(QHttpRequest*, QHttpResponse*)), 18 | this, SLOT(handleRequest(QHttpRequest*, QHttpResponse*))); 19 | 20 | server->listen(QHostAddress::Any, 8080); 21 | } 22 | 23 | void BodyData::handleRequest(QHttpRequest *req, QHttpResponse *resp) 24 | { 25 | new Responder(req, resp); 26 | } 27 | 28 | /// Responder 29 | 30 | Responder::Responder(QHttpRequest *req, QHttpResponse *resp) 31 | : m_req(req) 32 | , m_resp(resp) 33 | { 34 | QRegExp exp("^/user/([a-z]+$)"); 35 | if (exp.indexIn(req->path()) == -1) 36 | { 37 | resp->writeHead(403); 38 | resp->end(QByteArray("You aren't allowed here!")); 39 | /// @todo There should be a way to tell request to stop streaming data 40 | return; 41 | } 42 | 43 | resp->setHeader("Content-Type", "text/html"); 44 | resp->writeHead(200); 45 | 46 | QString name = exp.capturedTexts()[1]; 47 | QString bodyStart = tr("BodyData App

Hello %1!

").arg(name); 48 | resp->write(bodyStart.toUtf8()); 49 | 50 | connect(req, SIGNAL(data(const QByteArray&)), this, SLOT(accumulate(const QByteArray&))); 51 | connect(req, SIGNAL(end()), this, SLOT(reply())); 52 | connect(m_resp, SIGNAL(done()), this, SLOT(deleteLater())); 53 | } 54 | 55 | Responder::~Responder() 56 | { 57 | } 58 | 59 | void Responder::accumulate(const QByteArray &data) 60 | { 61 | m_resp->write(data); 62 | } 63 | 64 | void Responder::reply() 65 | { 66 | m_resp->end(QByteArray("

")); 67 | } 68 | 69 | /// main 70 | 71 | int main(int argc, char **argv) 72 | { 73 | QCoreApplication app(argc, argv); 74 | BodyData bodydata; 75 | app.exec(); 76 | } 77 | -------------------------------------------------------------------------------- /examples/bodydata/bodydata.h: -------------------------------------------------------------------------------- 1 | #include "qhttpserverfwd.h" 2 | 3 | #include 4 | #include 5 | 6 | /// BodyData 7 | 8 | class BodyData : public QObject 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | BodyData(); 14 | 15 | private slots: 16 | void handleRequest(QHttpRequest *req, QHttpResponse *resp); 17 | }; 18 | 19 | /// Responder 20 | 21 | class Responder : public QObject 22 | { 23 | Q_OBJECT 24 | 25 | public: 26 | Responder(QHttpRequest *req, QHttpResponse *resp); 27 | ~Responder(); 28 | 29 | signals: 30 | void done(); 31 | 32 | private slots: 33 | void accumulate(const QByteArray &data); 34 | void reply(); 35 | 36 | private: 37 | QScopedPointer m_req; 38 | QHttpResponse *m_resp; 39 | }; 40 | -------------------------------------------------------------------------------- /examples/bodydata/bodydata.pro: -------------------------------------------------------------------------------- 1 | TARGET = bodydata 2 | 3 | QT += network 4 | QT -= gui 5 | 6 | CONFIG += debug 7 | 8 | INCLUDEPATH += ../../src 9 | LIBS += -L../../lib 10 | 11 | win32 { 12 | debug: LIBS += -lqhttpserverd 13 | else: LIBS += -lqhttpserver 14 | } else { 15 | LIBS += -lqhttpserver 16 | } 17 | 18 | SOURCES = bodydata.cpp 19 | HEADERS = bodydata.h 20 | -------------------------------------------------------------------------------- /examples/examples.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | SUBDIRS += \ 3 | helloworld\ 4 | greeting\ 5 | bodydata\ 6 | -------------------------------------------------------------------------------- /examples/greeting/greeting.cpp: -------------------------------------------------------------------------------- 1 | #include "greeting.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | /// Greeting 12 | 13 | Greeting::Greeting() 14 | { 15 | QHttpServer *server = new QHttpServer(this); 16 | connect(server, SIGNAL(newRequest(QHttpRequest*, QHttpResponse*)), 17 | this, SLOT(handleRequest(QHttpRequest*, QHttpResponse*))); 18 | 19 | server->listen(QHostAddress::Any, 8080); 20 | } 21 | 22 | void Greeting::handleRequest(QHttpRequest *req, QHttpResponse *resp) 23 | { 24 | QRegExp exp("^/user/([a-z]+)$"); 25 | if( exp.indexIn(req->path()) != -1 ) 26 | { 27 | resp->setHeader("Content-Type", "text/html"); 28 | resp->writeHead(200); 29 | 30 | QString name = exp.capturedTexts()[1]; 31 | QString body = tr("Greeting App

Hello %1!

"); 32 | resp->end(body.arg(name).toUtf8()); 33 | } 34 | else 35 | { 36 | resp->writeHead(403); 37 | resp->end(QByteArray("You aren't allowed here!")); 38 | } 39 | } 40 | 41 | /// main 42 | 43 | int main(int argc, char **argv) 44 | { 45 | QCoreApplication app(argc, argv); 46 | Greeting greeting; 47 | app.exec(); 48 | } 49 | -------------------------------------------------------------------------------- /examples/greeting/greeting.h: -------------------------------------------------------------------------------- 1 | #include "qhttpserverfwd.h" 2 | 3 | #include 4 | 5 | /// Greeting 6 | 7 | class Greeting : public QObject 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | Greeting(); 13 | 14 | private slots: 15 | void handleRequest(QHttpRequest *req, QHttpResponse *resp); 16 | }; 17 | -------------------------------------------------------------------------------- /examples/greeting/greeting.pro: -------------------------------------------------------------------------------- 1 | TARGET = greeting 2 | 3 | QT += network 4 | QT -= gui 5 | 6 | CONFIG += debug 7 | 8 | INCLUDEPATH += ../../src 9 | LIBS += -L../../lib 10 | 11 | win32 { 12 | debug: LIBS += -lqhttpserverd 13 | else: LIBS += -lqhttpserver 14 | } else { 15 | LIBS += -lqhttpserver 16 | } 17 | 18 | SOURCES = greeting.cpp 19 | HEADERS = greeting.h 20 | -------------------------------------------------------------------------------- /examples/helloworld/helloworld.cpp: -------------------------------------------------------------------------------- 1 | #include "helloworld.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | /// HelloWorld 10 | 11 | HelloWorld::HelloWorld() 12 | { 13 | QHttpServer *server = new QHttpServer(this); 14 | connect(server, SIGNAL(newRequest(QHttpRequest*, QHttpResponse*)), 15 | this, SLOT(handleRequest(QHttpRequest*, QHttpResponse*))); 16 | 17 | server->listen(QHostAddress::Any, 8080); 18 | } 19 | 20 | void HelloWorld::handleRequest(QHttpRequest *req, QHttpResponse *resp) 21 | { 22 | Q_UNUSED(req); 23 | 24 | QByteArray body = "Hello World"; 25 | resp->setHeader("Content-Length", QString::number(body.size())); 26 | resp->writeHead(200); 27 | resp->end(body); 28 | } 29 | 30 | /// main 31 | 32 | int main(int argc, char **argv) 33 | { 34 | QCoreApplication app(argc, argv); 35 | HelloWorld hello; 36 | app.exec(); 37 | } 38 | -------------------------------------------------------------------------------- /examples/helloworld/helloworld.h: -------------------------------------------------------------------------------- 1 | #include "qhttpserverfwd.h" 2 | 3 | #include 4 | 5 | /// HelloWorld 6 | 7 | class HelloWorld : public QObject 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | HelloWorld(); 13 | 14 | private slots: 15 | void handleRequest(QHttpRequest *req, QHttpResponse *resp); 16 | }; 17 | -------------------------------------------------------------------------------- /examples/helloworld/helloworld.pro: -------------------------------------------------------------------------------- 1 | TARGET = helloworld 2 | 3 | QT += network 4 | QT -= gui 5 | 6 | CONFIG += debug 7 | 8 | INCLUDEPATH += ../../src 9 | LIBS += -L../../lib 10 | 11 | win32 { 12 | debug: LIBS += -lqhttpserverd 13 | else: LIBS += -lqhttpserver 14 | } else { 15 | LIBS += -lqhttpserver 16 | } 17 | 18 | SOURCES = helloworld.cpp 19 | HEADERS = helloworld.h 20 | -------------------------------------------------------------------------------- /http-parser/.gitignore: -------------------------------------------------------------------------------- 1 | /out/ 2 | core 3 | tags 4 | *.o 5 | test 6 | test_g 7 | test_fast 8 | bench 9 | url_parser 10 | parsertrace 11 | parsertrace_g 12 | *.mk 13 | *.Makefile 14 | *.so.* 15 | *.a 16 | 17 | 18 | # Visual Studio uglies 19 | *.suo 20 | *.sln 21 | *.vcxproj 22 | *.vcxproj.filters 23 | *.vcxproj.user 24 | *.opensdf 25 | *.ncrunchsolution* 26 | *.sdf 27 | *.vsp 28 | *.psess 29 | -------------------------------------------------------------------------------- /http-parser/.mailmap: -------------------------------------------------------------------------------- 1 | # update AUTHORS with: 2 | # git log --all --reverse --format='%aN <%aE>' | perl -ne 'BEGIN{print "# Authors ordered by first contribution.\n"} print unless $h{$_}; $h{$_} = 1' > AUTHORS 3 | Ryan Dahl 4 | Salman Haq 5 | Simon Zimmermann 6 | Thomas LE ROUX LE ROUX Thomas 7 | Thomas LE ROUX Thomas LE ROUX 8 | Fedor Indutny 9 | -------------------------------------------------------------------------------- /http-parser/.travis.yml: -------------------------------------------------------------------------------- 1 | language: c 2 | 3 | compiler: 4 | - clang 5 | - gcc 6 | 7 | script: 8 | - "make" 9 | 10 | notifications: 11 | email: false 12 | irc: 13 | - "irc.freenode.net#node-ci" 14 | -------------------------------------------------------------------------------- /http-parser/AUTHORS: -------------------------------------------------------------------------------- 1 | # Authors ordered by first contribution. 2 | Ryan Dahl 3 | Jeremy Hinegardner 4 | Sergey Shepelev 5 | Joe Damato 6 | tomika 7 | Phoenix Sol 8 | Cliff Frey 9 | Ewen Cheslack-Postava 10 | Santiago Gala 11 | Tim Becker 12 | Jeff Terrace 13 | Ben Noordhuis 14 | Nathan Rajlich 15 | Mark Nottingham 16 | Aman Gupta 17 | Tim Becker 18 | Sean Cunningham 19 | Peter Griess 20 | Salman Haq 21 | Cliff Frey 22 | Jon Kolb 23 | Fouad Mardini 24 | Paul Querna 25 | Felix Geisendörfer 26 | koichik 27 | Andre Caron 28 | Ivo Raisr 29 | James McLaughlin 30 | David Gwynne 31 | Thomas LE ROUX 32 | Randy Rizun 33 | Andre Louis Caron 34 | Simon Zimmermann 35 | Erik Dubbelboer 36 | Martell Malone 37 | Bertrand Paquet 38 | BogDan Vatra 39 | Peter Faiman 40 | Corey Richardson 41 | Tóth Tamás 42 | Cam Swords 43 | Chris Dickinson 44 | Uli Köhler 45 | Charlie Somerville 46 | Patrik Stutz 47 | Fedor Indutny 48 | runner 49 | Alexis Campailla 50 | David Wragg 51 | Vinnie Falco 52 | Alex Butum 53 | Rex Feng 54 | Alex Kocharin 55 | Mark Koopman 56 | Helge Heß 57 | Alexis La Goutte 58 | George Miroshnykov 59 | Maciej Małecki 60 | Marc O'Morain 61 | Jeff Pinner 62 | Timothy J Fontaine 63 | Akagi201 64 | Romain Giraud 65 | Jay Satiro 66 | Arne Steen 67 | Kjell Schubert 68 | -------------------------------------------------------------------------------- /http-parser/LICENSE-MIT: -------------------------------------------------------------------------------- 1 | http_parser.c is based on src/http/ngx_http_parse.c from NGINX copyright 2 | Igor Sysoev. 3 | 4 | Additional changes are licensed under the same terms as NGINX and 5 | copyright Joyent, Inc. and other Node contributors. All rights reserved. 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to 9 | deal in the Software without restriction, including without limitation the 10 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 11 | sell copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 23 | IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /http-parser/README.md: -------------------------------------------------------------------------------- 1 | HTTP Parser 2 | =========== 3 | 4 | [![Build Status](https://travis-ci.org/joyent/http-parser.png?branch=master)](https://travis-ci.org/joyent/http-parser) 5 | 6 | This is a parser for HTTP messages written in C. It parses both requests and 7 | responses. The parser is designed to be used in performance HTTP 8 | applications. It does not make any syscalls nor allocations, it does not 9 | buffer data, it can be interrupted at anytime. Depending on your 10 | architecture, it only requires about 40 bytes of data per message 11 | stream (in a web server that is per connection). 12 | 13 | Features: 14 | 15 | * No dependencies 16 | * Handles persistent streams (keep-alive). 17 | * Decodes chunked encoding. 18 | * Upgrade support 19 | * Defends against buffer overflow attacks. 20 | 21 | The parser extracts the following information from HTTP messages: 22 | 23 | * Header fields and values 24 | * Content-Length 25 | * Request method 26 | * Response status code 27 | * Transfer-Encoding 28 | * HTTP version 29 | * Request URL 30 | * Message body 31 | 32 | 33 | Usage 34 | ----- 35 | 36 | One `http_parser` object is used per TCP connection. Initialize the struct 37 | using `http_parser_init()` and set the callbacks. That might look something 38 | like this for a request parser: 39 | ```c 40 | http_parser_settings settings; 41 | settings.on_url = my_url_callback; 42 | settings.on_header_field = my_header_field_callback; 43 | /* ... */ 44 | 45 | http_parser *parser = malloc(sizeof(http_parser)); 46 | http_parser_init(parser, HTTP_REQUEST); 47 | parser->data = my_socket; 48 | ``` 49 | 50 | When data is received on the socket execute the parser and check for errors. 51 | 52 | ```c 53 | size_t len = 80*1024, nparsed; 54 | char buf[len]; 55 | ssize_t recved; 56 | 57 | recved = recv(fd, buf, len, 0); 58 | 59 | if (recved < 0) { 60 | /* Handle error. */ 61 | } 62 | 63 | /* Start up / continue the parser. 64 | * Note we pass recved==0 to signal that EOF has been received. 65 | */ 66 | nparsed = http_parser_execute(parser, &settings, buf, recved); 67 | 68 | if (parser->upgrade) { 69 | /* handle new protocol */ 70 | } else if (nparsed != recved) { 71 | /* Handle error. Usually just close the connection. */ 72 | } 73 | ``` 74 | 75 | HTTP needs to know where the end of the stream is. For example, sometimes 76 | servers send responses without Content-Length and expect the client to 77 | consume input (for the body) until EOF. To tell http_parser about EOF, give 78 | `0` as the fourth parameter to `http_parser_execute()`. Callbacks and errors 79 | can still be encountered during an EOF, so one must still be prepared 80 | to receive them. 81 | 82 | Scalar valued message information such as `status_code`, `method`, and the 83 | HTTP version are stored in the parser structure. This data is only 84 | temporally stored in `http_parser` and gets reset on each new message. If 85 | this information is needed later, copy it out of the structure during the 86 | `headers_complete` callback. 87 | 88 | The parser decodes the transfer-encoding for both requests and responses 89 | transparently. That is, a chunked encoding is decoded before being sent to 90 | the on_body callback. 91 | 92 | 93 | The Special Problem of Upgrade 94 | ------------------------------ 95 | 96 | HTTP supports upgrading the connection to a different protocol. An 97 | increasingly common example of this is the Web Socket protocol which sends 98 | a request like 99 | 100 | GET /demo HTTP/1.1 101 | Upgrade: WebSocket 102 | Connection: Upgrade 103 | Host: example.com 104 | Origin: http://example.com 105 | WebSocket-Protocol: sample 106 | 107 | followed by non-HTTP data. 108 | 109 | (See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more 110 | information the Web Socket protocol.) 111 | 112 | To support this, the parser will treat this as a normal HTTP message without a 113 | body, issuing both on_headers_complete and on_message_complete callbacks. However 114 | http_parser_execute() will stop parsing at the end of the headers and return. 115 | 116 | The user is expected to check if `parser->upgrade` has been set to 1 after 117 | `http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied 118 | offset by the return value of `http_parser_execute()`. 119 | 120 | 121 | Callbacks 122 | --------- 123 | 124 | During the `http_parser_execute()` call, the callbacks set in 125 | `http_parser_settings` will be executed. The parser maintains state and 126 | never looks behind, so buffering the data is not necessary. If you need to 127 | save certain data for later usage, you can do that from the callbacks. 128 | 129 | There are two types of callbacks: 130 | 131 | * notification `typedef int (*http_cb) (http_parser*);` 132 | Callbacks: on_message_begin, on_headers_complete, on_message_complete. 133 | * data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);` 134 | Callbacks: (requests only) on_url, 135 | (common) on_header_field, on_header_value, on_body; 136 | 137 | Callbacks must return 0 on success. Returning a non-zero value indicates 138 | error to the parser, making it exit immediately. 139 | 140 | In case you parse HTTP message in chunks (i.e. `read()` request line 141 | from socket, parse, read half headers, parse, etc) your data callbacks 142 | may be called more than once. Http-parser guarantees that data pointer is only 143 | valid for the lifetime of callback. You can also `read()` into a heap allocated 144 | buffer to avoid copying memory around if this fits your application. 145 | 146 | Reading headers may be a tricky task if you read/parse headers partially. 147 | Basically, you need to remember whether last header callback was field or value 148 | and apply the following logic: 149 | 150 | (on_header_field and on_header_value shortened to on_h_*) 151 | ------------------------ ------------ -------------------------------------------- 152 | | State (prev. callback) | Callback | Description/action | 153 | ------------------------ ------------ -------------------------------------------- 154 | | nothing (first call) | on_h_field | Allocate new buffer and copy callback data | 155 | | | | into it | 156 | ------------------------ ------------ -------------------------------------------- 157 | | value | on_h_field | New header started. | 158 | | | | Copy current name,value buffers to headers | 159 | | | | list and allocate new buffer for new name | 160 | ------------------------ ------------ -------------------------------------------- 161 | | field | on_h_field | Previous name continues. Reallocate name | 162 | | | | buffer and append callback data to it | 163 | ------------------------ ------------ -------------------------------------------- 164 | | field | on_h_value | Value for current header started. Allocate | 165 | | | | new buffer and copy callback data to it | 166 | ------------------------ ------------ -------------------------------------------- 167 | | value | on_h_value | Value continues. Reallocate value buffer | 168 | | | | and append callback data to it | 169 | ------------------------ ------------ -------------------------------------------- 170 | 171 | 172 | Parsing URLs 173 | ------------ 174 | 175 | A simplistic zero-copy URL parser is provided as `http_parser_parse_url()`. 176 | Users of this library may wish to use it to parse URLs constructed from 177 | consecutive `on_url` callbacks. 178 | 179 | See examples of reading in headers: 180 | 181 | * [partial example](http://gist.github.com/155877) in C 182 | * [from http-parser tests](http://github.com/joyent/http-parser/blob/37a0ff8/test.c#L403) in C 183 | * [from Node library](http://github.com/joyent/node/blob/842eaf4/src/http.js#L284) in Javascript 184 | -------------------------------------------------------------------------------- /http-parser/bench.c: -------------------------------------------------------------------------------- 1 | /* Copyright Fedor Indutny. All rights reserved. 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #include "http_parser.h" 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | static const char data[] = 28 | "POST /joyent/http-parser HTTP/1.1\r\n" 29 | "Host: github.com\r\n" 30 | "DNT: 1\r\n" 31 | "Accept-Encoding: gzip, deflate, sdch\r\n" 32 | "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n" 33 | "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) " 34 | "AppleWebKit/537.36 (KHTML, like Gecko) " 35 | "Chrome/39.0.2171.65 Safari/537.36\r\n" 36 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9," 37 | "image/webp,*/*;q=0.8\r\n" 38 | "Referer: https://github.com/joyent/http-parser\r\n" 39 | "Connection: keep-alive\r\n" 40 | "Transfer-Encoding: chunked\r\n" 41 | "Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n\r\n"; 42 | static const size_t data_len = sizeof(data) - 1; 43 | 44 | static int on_info(http_parser* p) { 45 | return 0; 46 | } 47 | 48 | 49 | static int on_data(http_parser* p, const char *at, size_t length) { 50 | return 0; 51 | } 52 | 53 | static http_parser_settings settings = { 54 | .on_message_begin = on_info, 55 | .on_headers_complete = on_info, 56 | .on_message_complete = on_info, 57 | .on_header_field = on_data, 58 | .on_header_value = on_data, 59 | .on_url = on_data, 60 | .on_status = on_data, 61 | .on_body = on_data 62 | }; 63 | 64 | int bench(int iter_count, int silent) { 65 | struct http_parser parser; 66 | int i; 67 | int err; 68 | struct timeval start; 69 | struct timeval end; 70 | float rps; 71 | 72 | if (!silent) { 73 | err = gettimeofday(&start, NULL); 74 | assert(err == 0); 75 | } 76 | 77 | for (i = 0; i < iter_count; i++) { 78 | size_t parsed; 79 | http_parser_init(&parser, HTTP_REQUEST); 80 | 81 | parsed = http_parser_execute(&parser, &settings, data, data_len); 82 | assert(parsed == data_len); 83 | } 84 | 85 | if (!silent) { 86 | err = gettimeofday(&end, NULL); 87 | assert(err == 0); 88 | 89 | fprintf(stdout, "Benchmark result:\n"); 90 | 91 | rps = (float) (end.tv_sec - start.tv_sec) + 92 | (end.tv_usec - start.tv_usec) * 1e-6f; 93 | fprintf(stdout, "Took %f seconds to run\n", rps); 94 | 95 | rps = (float) iter_count / rps; 96 | fprintf(stdout, "%f req/sec\n", rps); 97 | fflush(stdout); 98 | } 99 | 100 | return 0; 101 | } 102 | 103 | int main(int argc, char** argv) { 104 | if (argc == 2 && strcmp(argv[1], "infinite") == 0) { 105 | for (;;) 106 | bench(5000000, 1); 107 | return 0; 108 | } else { 109 | return bench(5000000, 0); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /http-parser/contrib/parsertrace.c: -------------------------------------------------------------------------------- 1 | /* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev 2 | * 3 | * Additional changes are licensed under the same terms as NGINX and 4 | * copyright Joyent, Inc. and other Node contributors. All rights reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | 25 | /* Dump what the parser finds to stdout as it happen */ 26 | 27 | #include "http_parser.h" 28 | #include 29 | #include 30 | #include 31 | 32 | int on_message_begin(http_parser* _) { 33 | (void)_; 34 | printf("\n***MESSAGE BEGIN***\n\n"); 35 | return 0; 36 | } 37 | 38 | int on_headers_complete(http_parser* _) { 39 | (void)_; 40 | printf("\n***HEADERS COMPLETE***\n\n"); 41 | return 0; 42 | } 43 | 44 | int on_message_complete(http_parser* _) { 45 | (void)_; 46 | printf("\n***MESSAGE COMPLETE***\n\n"); 47 | return 0; 48 | } 49 | 50 | int on_url(http_parser* _, const char* at, size_t length) { 51 | (void)_; 52 | printf("Url: %.*s\n", (int)length, at); 53 | return 0; 54 | } 55 | 56 | int on_header_field(http_parser* _, const char* at, size_t length) { 57 | (void)_; 58 | printf("Header field: %.*s\n", (int)length, at); 59 | return 0; 60 | } 61 | 62 | int on_header_value(http_parser* _, const char* at, size_t length) { 63 | (void)_; 64 | printf("Header value: %.*s\n", (int)length, at); 65 | return 0; 66 | } 67 | 68 | int on_body(http_parser* _, const char* at, size_t length) { 69 | (void)_; 70 | printf("Body: %.*s\n", (int)length, at); 71 | return 0; 72 | } 73 | 74 | void usage(const char* name) { 75 | fprintf(stderr, 76 | "Usage: %s $type $filename\n" 77 | " type: -x, where x is one of {r,b,q}\n" 78 | " parses file as a Response, reQuest, or Both\n", 79 | name); 80 | exit(EXIT_FAILURE); 81 | } 82 | 83 | int main(int argc, char* argv[]) { 84 | enum http_parser_type file_type; 85 | 86 | if (argc != 3) { 87 | usage(argv[0]); 88 | } 89 | 90 | char* type = argv[1]; 91 | if (type[0] != '-') { 92 | usage(argv[0]); 93 | } 94 | 95 | switch (type[1]) { 96 | /* in the case of "-", type[1] will be NUL */ 97 | case 'r': 98 | file_type = HTTP_RESPONSE; 99 | break; 100 | case 'q': 101 | file_type = HTTP_REQUEST; 102 | break; 103 | case 'b': 104 | file_type = HTTP_BOTH; 105 | break; 106 | default: 107 | usage(argv[0]); 108 | } 109 | 110 | char* filename = argv[2]; 111 | FILE* file = fopen(filename, "r"); 112 | if (file == NULL) { 113 | perror("fopen"); 114 | goto fail; 115 | } 116 | 117 | fseek(file, 0, SEEK_END); 118 | long file_length = ftell(file); 119 | if (file_length == -1) { 120 | perror("ftell"); 121 | goto fail; 122 | } 123 | fseek(file, 0, SEEK_SET); 124 | 125 | char* data = malloc(file_length); 126 | if (fread(data, 1, file_length, file) != (size_t)file_length) { 127 | fprintf(stderr, "couldn't read entire file\n"); 128 | free(data); 129 | goto fail; 130 | } 131 | 132 | http_parser_settings settings; 133 | memset(&settings, 0, sizeof(settings)); 134 | settings.on_message_begin = on_message_begin; 135 | settings.on_url = on_url; 136 | settings.on_header_field = on_header_field; 137 | settings.on_header_value = on_header_value; 138 | settings.on_headers_complete = on_headers_complete; 139 | settings.on_body = on_body; 140 | settings.on_message_complete = on_message_complete; 141 | 142 | http_parser parser; 143 | http_parser_init(&parser, file_type); 144 | size_t nparsed = http_parser_execute(&parser, &settings, data, file_length); 145 | free(data); 146 | 147 | if (nparsed != (size_t)file_length) { 148 | fprintf(stderr, 149 | "Error: %s (%s)\n", 150 | http_errno_description(HTTP_PARSER_ERRNO(&parser)), 151 | http_errno_name(HTTP_PARSER_ERRNO(&parser))); 152 | goto fail; 153 | } 154 | 155 | return EXIT_SUCCESS; 156 | 157 | fail: 158 | fclose(file); 159 | return EXIT_FAILURE; 160 | } 161 | -------------------------------------------------------------------------------- /http-parser/contrib/url_parser.c: -------------------------------------------------------------------------------- 1 | #include "http_parser.h" 2 | #include 3 | #include 4 | 5 | void 6 | dump_url (const char *url, const struct http_parser_url *u) 7 | { 8 | unsigned int i; 9 | 10 | printf("\tfield_set: 0x%x, port: %u\n", u->field_set, u->port); 11 | for (i = 0; i < UF_MAX; i++) { 12 | if ((u->field_set & (1 << i)) == 0) { 13 | printf("\tfield_data[%u]: unset\n", i); 14 | continue; 15 | } 16 | 17 | printf("\tfield_data[%u]: off: %u, len: %u, part: %.*s\n", 18 | i, 19 | u->field_data[i].off, 20 | u->field_data[i].len, 21 | u->field_data[i].len, 22 | url + u->field_data[i].off); 23 | } 24 | } 25 | 26 | int main(int argc, char ** argv) { 27 | struct http_parser_url u; 28 | int len, connect, result; 29 | 30 | if (argc != 3) { 31 | printf("Syntax : %s connect|get url\n", argv[0]); 32 | return 1; 33 | } 34 | len = strlen(argv[2]); 35 | connect = strcmp("connect", argv[1]) == 0 ? 1 : 0; 36 | printf("Parsing %s, connect %d\n", argv[2], connect); 37 | 38 | result = http_parser_parse_url(argv[2], len, connect, &u); 39 | if (result != 0) { 40 | printf("Parse error : %d\n", result); 41 | return result; 42 | } 43 | printf("Parse ok, result : \n"); 44 | dump_url(argv[2], &u); 45 | return 0; 46 | } -------------------------------------------------------------------------------- /http-parser/http_parser.c: -------------------------------------------------------------------------------- 1 | /* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev 2 | * 3 | * Additional changes are licensed under the same terms as NGINX and 4 | * copyright Joyent, Inc. and other Node contributors. All rights reserved. 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | #include "http_parser.h" 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #ifndef ULLONG_MAX 33 | # define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ 34 | #endif 35 | 36 | #ifndef MIN 37 | # define MIN(a,b) ((a) < (b) ? (a) : (b)) 38 | #endif 39 | 40 | #ifndef ARRAY_SIZE 41 | # define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) 42 | #endif 43 | 44 | #ifndef BIT_AT 45 | # define BIT_AT(a, i) \ 46 | (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ 47 | (1 << ((unsigned int) (i) & 7)))) 48 | #endif 49 | 50 | #ifndef ELEM_AT 51 | # define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) 52 | #endif 53 | 54 | #define SET_ERRNO(e) \ 55 | do { \ 56 | parser->http_errno = (e); \ 57 | } while(0) 58 | 59 | #define CURRENT_STATE() p_state 60 | #define UPDATE_STATE(V) p_state = (enum state) (V); 61 | #define RETURN(V) \ 62 | do { \ 63 | parser->state = CURRENT_STATE(); \ 64 | return (V); \ 65 | } while (0); 66 | #define REEXECUTE() \ 67 | goto reexecute; \ 68 | 69 | 70 | #ifdef __GNUC__ 71 | # define LIKELY(X) __builtin_expect(!!(X), 1) 72 | # define UNLIKELY(X) __builtin_expect(!!(X), 0) 73 | #else 74 | # define LIKELY(X) (X) 75 | # define UNLIKELY(X) (X) 76 | #endif 77 | 78 | 79 | /* Run the notify callback FOR, returning ER if it fails */ 80 | #define CALLBACK_NOTIFY_(FOR, ER) \ 81 | do { \ 82 | assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ 83 | \ 84 | if (LIKELY(settings->on_##FOR)) { \ 85 | parser->state = CURRENT_STATE(); \ 86 | if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ 87 | SET_ERRNO(HPE_CB_##FOR); \ 88 | } \ 89 | UPDATE_STATE(parser->state); \ 90 | \ 91 | /* We either errored above or got paused; get out */ \ 92 | if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ 93 | return (ER); \ 94 | } \ 95 | } \ 96 | } while (0) 97 | 98 | /* Run the notify callback FOR and consume the current byte */ 99 | #define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) 100 | 101 | /* Run the notify callback FOR and don't consume the current byte */ 102 | #define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) 103 | 104 | /* Run data callback FOR with LEN bytes, returning ER if it fails */ 105 | #define CALLBACK_DATA_(FOR, LEN, ER) \ 106 | do { \ 107 | assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ 108 | \ 109 | if (FOR##_mark) { \ 110 | if (LIKELY(settings->on_##FOR)) { \ 111 | parser->state = CURRENT_STATE(); \ 112 | if (UNLIKELY(0 != \ 113 | settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ 114 | SET_ERRNO(HPE_CB_##FOR); \ 115 | } \ 116 | UPDATE_STATE(parser->state); \ 117 | \ 118 | /* We either errored above or got paused; get out */ \ 119 | if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ 120 | return (ER); \ 121 | } \ 122 | } \ 123 | FOR##_mark = NULL; \ 124 | } \ 125 | } while (0) 126 | 127 | /* Run the data callback FOR and consume the current byte */ 128 | #define CALLBACK_DATA(FOR) \ 129 | CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) 130 | 131 | /* Run the data callback FOR and don't consume the current byte */ 132 | #define CALLBACK_DATA_NOADVANCE(FOR) \ 133 | CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) 134 | 135 | /* Set the mark FOR; non-destructive if mark is already set */ 136 | #define MARK(FOR) \ 137 | do { \ 138 | if (!FOR##_mark) { \ 139 | FOR##_mark = p; \ 140 | } \ 141 | } while (0) 142 | 143 | /* Don't allow the total size of the HTTP headers (including the status 144 | * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect 145 | * embedders against denial-of-service attacks where the attacker feeds 146 | * us a never-ending header that the embedder keeps buffering. 147 | * 148 | * This check is arguably the responsibility of embedders but we're doing 149 | * it on the embedder's behalf because most won't bother and this way we 150 | * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger 151 | * than any reasonable request or response so this should never affect 152 | * day-to-day operation. 153 | */ 154 | #define COUNT_HEADER_SIZE(V) \ 155 | do { \ 156 | parser->nread += (V); \ 157 | if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ 158 | SET_ERRNO(HPE_HEADER_OVERFLOW); \ 159 | goto error; \ 160 | } \ 161 | } while (0) 162 | 163 | 164 | #define PROXY_CONNECTION "proxy-connection" 165 | #define CONNECTION "connection" 166 | #define CONTENT_LENGTH "content-length" 167 | #define TRANSFER_ENCODING "transfer-encoding" 168 | #define UPGRADE "upgrade" 169 | #define CHUNKED "chunked" 170 | #define KEEP_ALIVE "keep-alive" 171 | #define CLOSE "close" 172 | 173 | 174 | static const char *method_strings[] = 175 | { 176 | #define XX(num, name, string) #string, 177 | HTTP_METHOD_MAP(XX) 178 | #undef XX 179 | }; 180 | 181 | 182 | /* Tokens as defined by rfc 2616. Also lowercases them. 183 | * token = 1* 184 | * separators = "(" | ")" | "<" | ">" | "@" 185 | * | "," | ";" | ":" | "\" | <"> 186 | * | "/" | "[" | "]" | "?" | "=" 187 | * | "{" | "}" | SP | HT 188 | */ 189 | static const char tokens[256] = { 190 | /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ 191 | 0, 0, 0, 0, 0, 0, 0, 0, 192 | /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ 193 | 0, 0, 0, 0, 0, 0, 0, 0, 194 | /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ 195 | 0, 0, 0, 0, 0, 0, 0, 0, 196 | /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ 197 | 0, 0, 0, 0, 0, 0, 0, 0, 198 | /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ 199 | 0, '!', 0, '#', '$', '%', '&', '\'', 200 | /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ 201 | 0, 0, '*', '+', 0, '-', '.', 0, 202 | /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ 203 | '0', '1', '2', '3', '4', '5', '6', '7', 204 | /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ 205 | '8', '9', 0, 0, 0, 0, 0, 0, 206 | /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ 207 | 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 208 | /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ 209 | 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 210 | /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ 211 | 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 212 | /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ 213 | 'x', 'y', 'z', 0, 0, 0, '^', '_', 214 | /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ 215 | '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 216 | /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ 217 | 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 218 | /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ 219 | 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 220 | /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ 221 | 'x', 'y', 'z', 0, '|', 0, '~', 0 }; 222 | 223 | 224 | static const int8_t unhex[256] = 225 | {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 226 | ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 227 | ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 228 | , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 229 | ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 230 | ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 231 | ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 232 | ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 233 | }; 234 | 235 | 236 | #if HTTP_PARSER_STRICT 237 | # define T(v) 0 238 | #else 239 | # define T(v) v 240 | #endif 241 | 242 | 243 | static const uint8_t normal_url_char[32] = { 244 | /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ 245 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, 246 | /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ 247 | 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, 248 | /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ 249 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, 250 | /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ 251 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, 252 | /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ 253 | 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, 254 | /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ 255 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 256 | /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ 257 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 258 | /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ 259 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, 260 | /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ 261 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 262 | /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ 263 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 264 | /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ 265 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 266 | /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ 267 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 268 | /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ 269 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 270 | /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ 271 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 272 | /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ 273 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 274 | /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ 275 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; 276 | 277 | #undef T 278 | 279 | enum state 280 | { s_dead = 1 /* important that this is > 0 */ 281 | 282 | , s_start_req_or_res 283 | , s_res_or_resp_H 284 | , s_start_res 285 | , s_res_H 286 | , s_res_HT 287 | , s_res_HTT 288 | , s_res_HTTP 289 | , s_res_first_http_major 290 | , s_res_http_major 291 | , s_res_first_http_minor 292 | , s_res_http_minor 293 | , s_res_first_status_code 294 | , s_res_status_code 295 | , s_res_status_start 296 | , s_res_status 297 | , s_res_line_almost_done 298 | 299 | , s_start_req 300 | 301 | , s_req_method 302 | , s_req_spaces_before_url 303 | , s_req_schema 304 | , s_req_schema_slash 305 | , s_req_schema_slash_slash 306 | , s_req_server_start 307 | , s_req_server 308 | , s_req_server_with_at 309 | , s_req_path 310 | , s_req_query_string_start 311 | , s_req_query_string 312 | , s_req_fragment_start 313 | , s_req_fragment 314 | , s_req_http_start 315 | , s_req_http_H 316 | , s_req_http_HT 317 | , s_req_http_HTT 318 | , s_req_http_HTTP 319 | , s_req_first_http_major 320 | , s_req_http_major 321 | , s_req_first_http_minor 322 | , s_req_http_minor 323 | , s_req_line_almost_done 324 | 325 | , s_header_field_start 326 | , s_header_field 327 | , s_header_value_discard_ws 328 | , s_header_value_discard_ws_almost_done 329 | , s_header_value_discard_lws 330 | , s_header_value_start 331 | , s_header_value 332 | , s_header_value_lws 333 | 334 | , s_header_almost_done 335 | 336 | , s_chunk_size_start 337 | , s_chunk_size 338 | , s_chunk_parameters 339 | , s_chunk_size_almost_done 340 | 341 | , s_headers_almost_done 342 | , s_headers_done 343 | 344 | /* Important: 's_headers_done' must be the last 'header' state. All 345 | * states beyond this must be 'body' states. It is used for overflow 346 | * checking. See the PARSING_HEADER() macro. 347 | */ 348 | 349 | , s_chunk_data 350 | , s_chunk_data_almost_done 351 | , s_chunk_data_done 352 | 353 | , s_body_identity 354 | , s_body_identity_eof 355 | 356 | , s_message_done 357 | }; 358 | 359 | 360 | #define PARSING_HEADER(state) (state <= s_headers_done) 361 | 362 | 363 | enum header_states 364 | { h_general = 0 365 | , h_C 366 | , h_CO 367 | , h_CON 368 | 369 | , h_matching_connection 370 | , h_matching_proxy_connection 371 | , h_matching_content_length 372 | , h_matching_transfer_encoding 373 | , h_matching_upgrade 374 | 375 | , h_connection 376 | , h_content_length 377 | , h_transfer_encoding 378 | , h_upgrade 379 | 380 | , h_matching_transfer_encoding_chunked 381 | , h_matching_connection_token_start 382 | , h_matching_connection_keep_alive 383 | , h_matching_connection_close 384 | , h_matching_connection_upgrade 385 | , h_matching_connection_token 386 | 387 | , h_transfer_encoding_chunked 388 | , h_connection_keep_alive 389 | , h_connection_close 390 | , h_connection_upgrade 391 | }; 392 | 393 | enum http_host_state 394 | { 395 | s_http_host_dead = 1 396 | , s_http_userinfo_start 397 | , s_http_userinfo 398 | , s_http_host_start 399 | , s_http_host_v6_start 400 | , s_http_host 401 | , s_http_host_v6 402 | , s_http_host_v6_end 403 | , s_http_host_port_start 404 | , s_http_host_port 405 | }; 406 | 407 | /* Macros for character classes; depends on strict-mode */ 408 | #define CR '\r' 409 | #define LF '\n' 410 | #define LOWER(c) (unsigned char)(c | 0x20) 411 | #define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') 412 | #define IS_NUM(c) ((c) >= '0' && (c) <= '9') 413 | #define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) 414 | #define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) 415 | #define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ 416 | (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ 417 | (c) == ')') 418 | #define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ 419 | (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ 420 | (c) == '$' || (c) == ',') 421 | 422 | #define STRICT_TOKEN(c) (tokens[(unsigned char)c]) 423 | 424 | #if HTTP_PARSER_STRICT 425 | #define TOKEN(c) (tokens[(unsigned char)c]) 426 | #define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) 427 | #define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') 428 | #else 429 | #define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) 430 | #define IS_URL_CHAR(c) \ 431 | (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) 432 | #define IS_HOST_CHAR(c) \ 433 | (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') 434 | #endif 435 | 436 | 437 | #define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) 438 | 439 | 440 | #if HTTP_PARSER_STRICT 441 | # define STRICT_CHECK(cond) \ 442 | do { \ 443 | if (cond) { \ 444 | SET_ERRNO(HPE_STRICT); \ 445 | goto error; \ 446 | } \ 447 | } while (0) 448 | # define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) 449 | #else 450 | # define STRICT_CHECK(cond) 451 | # define NEW_MESSAGE() start_state 452 | #endif 453 | 454 | 455 | /* Map errno values to strings for human-readable output */ 456 | #define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, 457 | static struct { 458 | const char *name; 459 | const char *description; 460 | } http_strerror_tab[] = { 461 | HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) 462 | }; 463 | #undef HTTP_STRERROR_GEN 464 | 465 | int http_message_needs_eof(const http_parser *parser); 466 | 467 | /* Our URL parser. 468 | * 469 | * This is designed to be shared by http_parser_execute() for URL validation, 470 | * hence it has a state transition + byte-for-byte interface. In addition, it 471 | * is meant to be embedded in http_parser_parse_url(), which does the dirty 472 | * work of turning state transitions URL components for its API. 473 | * 474 | * This function should only be invoked with non-space characters. It is 475 | * assumed that the caller cares about (and can detect) the transition between 476 | * URL and non-URL states by looking for these. 477 | */ 478 | static enum state 479 | parse_url_char(enum state s, const char ch) 480 | { 481 | if (ch == ' ' || ch == '\r' || ch == '\n') { 482 | return s_dead; 483 | } 484 | 485 | #if HTTP_PARSER_STRICT 486 | if (ch == '\t' || ch == '\f') { 487 | return s_dead; 488 | } 489 | #endif 490 | 491 | switch (s) { 492 | case s_req_spaces_before_url: 493 | /* Proxied requests are followed by scheme of an absolute URI (alpha). 494 | * All methods except CONNECT are followed by '/' or '*'. 495 | */ 496 | 497 | if (ch == '/' || ch == '*') { 498 | return s_req_path; 499 | } 500 | 501 | if (IS_ALPHA(ch)) { 502 | return s_req_schema; 503 | } 504 | 505 | break; 506 | 507 | case s_req_schema: 508 | if (IS_ALPHA(ch)) { 509 | return s; 510 | } 511 | 512 | if (ch == ':') { 513 | return s_req_schema_slash; 514 | } 515 | 516 | break; 517 | 518 | case s_req_schema_slash: 519 | if (ch == '/') { 520 | return s_req_schema_slash_slash; 521 | } 522 | 523 | break; 524 | 525 | case s_req_schema_slash_slash: 526 | if (ch == '/') { 527 | return s_req_server_start; 528 | } 529 | 530 | break; 531 | 532 | case s_req_server_with_at: 533 | if (ch == '@') { 534 | return s_dead; 535 | } 536 | 537 | /* FALLTHROUGH */ 538 | case s_req_server_start: 539 | case s_req_server: 540 | if (ch == '/') { 541 | return s_req_path; 542 | } 543 | 544 | if (ch == '?') { 545 | return s_req_query_string_start; 546 | } 547 | 548 | if (ch == '@') { 549 | return s_req_server_with_at; 550 | } 551 | 552 | if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { 553 | return s_req_server; 554 | } 555 | 556 | break; 557 | 558 | case s_req_path: 559 | if (IS_URL_CHAR(ch)) { 560 | return s; 561 | } 562 | 563 | switch (ch) { 564 | case '?': 565 | return s_req_query_string_start; 566 | 567 | case '#': 568 | return s_req_fragment_start; 569 | } 570 | 571 | break; 572 | 573 | case s_req_query_string_start: 574 | case s_req_query_string: 575 | if (IS_URL_CHAR(ch)) { 576 | return s_req_query_string; 577 | } 578 | 579 | switch (ch) { 580 | case '?': 581 | /* allow extra '?' in query string */ 582 | return s_req_query_string; 583 | 584 | case '#': 585 | return s_req_fragment_start; 586 | } 587 | 588 | break; 589 | 590 | case s_req_fragment_start: 591 | if (IS_URL_CHAR(ch)) { 592 | return s_req_fragment; 593 | } 594 | 595 | switch (ch) { 596 | case '?': 597 | return s_req_fragment; 598 | 599 | case '#': 600 | return s; 601 | } 602 | 603 | break; 604 | 605 | case s_req_fragment: 606 | if (IS_URL_CHAR(ch)) { 607 | return s; 608 | } 609 | 610 | switch (ch) { 611 | case '?': 612 | case '#': 613 | return s; 614 | } 615 | 616 | break; 617 | 618 | default: 619 | break; 620 | } 621 | 622 | /* We should never fall out of the switch above unless there's an error */ 623 | return s_dead; 624 | } 625 | 626 | size_t http_parser_execute (http_parser *parser, 627 | const http_parser_settings *settings, 628 | const char *data, 629 | size_t len) 630 | { 631 | char c, ch; 632 | int8_t unhex_val; 633 | const char *p = data; 634 | const char *header_field_mark = 0; 635 | const char *header_value_mark = 0; 636 | const char *url_mark = 0; 637 | const char *body_mark = 0; 638 | const char *status_mark = 0; 639 | enum state p_state = (enum state) parser->state; 640 | 641 | /* We're in an error state. Don't bother doing anything. */ 642 | if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { 643 | return 0; 644 | } 645 | 646 | if (len == 0) { 647 | switch (CURRENT_STATE()) { 648 | case s_body_identity_eof: 649 | /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if 650 | * we got paused. 651 | */ 652 | CALLBACK_NOTIFY_NOADVANCE(message_complete); 653 | return 0; 654 | 655 | case s_dead: 656 | case s_start_req_or_res: 657 | case s_start_res: 658 | case s_start_req: 659 | return 0; 660 | 661 | default: 662 | SET_ERRNO(HPE_INVALID_EOF_STATE); 663 | return 1; 664 | } 665 | } 666 | 667 | 668 | if (CURRENT_STATE() == s_header_field) 669 | header_field_mark = data; 670 | if (CURRENT_STATE() == s_header_value) 671 | header_value_mark = data; 672 | switch (CURRENT_STATE()) { 673 | case s_req_path: 674 | case s_req_schema: 675 | case s_req_schema_slash: 676 | case s_req_schema_slash_slash: 677 | case s_req_server_start: 678 | case s_req_server: 679 | case s_req_server_with_at: 680 | case s_req_query_string_start: 681 | case s_req_query_string: 682 | case s_req_fragment_start: 683 | case s_req_fragment: 684 | url_mark = data; 685 | break; 686 | case s_res_status: 687 | status_mark = data; 688 | break; 689 | default: 690 | break; 691 | } 692 | 693 | for (p=data; p != data + len; p++) { 694 | ch = *p; 695 | 696 | if (PARSING_HEADER(CURRENT_STATE())) 697 | COUNT_HEADER_SIZE(1); 698 | 699 | reexecute: 700 | switch (CURRENT_STATE()) { 701 | 702 | case s_dead: 703 | /* this state is used after a 'Connection: close' message 704 | * the parser will error out if it reads another message 705 | */ 706 | if (LIKELY(ch == CR || ch == LF)) 707 | break; 708 | 709 | SET_ERRNO(HPE_CLOSED_CONNECTION); 710 | goto error; 711 | 712 | case s_start_req_or_res: 713 | { 714 | if (ch == CR || ch == LF) 715 | break; 716 | parser->flags = 0; 717 | parser->content_length = ULLONG_MAX; 718 | 719 | if (ch == 'H') { 720 | UPDATE_STATE(s_res_or_resp_H); 721 | 722 | CALLBACK_NOTIFY(message_begin); 723 | } else { 724 | parser->type = HTTP_REQUEST; 725 | UPDATE_STATE(s_start_req); 726 | REEXECUTE(); 727 | } 728 | 729 | break; 730 | } 731 | 732 | case s_res_or_resp_H: 733 | if (ch == 'T') { 734 | parser->type = HTTP_RESPONSE; 735 | UPDATE_STATE(s_res_HT); 736 | } else { 737 | if (UNLIKELY(ch != 'E')) { 738 | SET_ERRNO(HPE_INVALID_CONSTANT); 739 | goto error; 740 | } 741 | 742 | parser->type = HTTP_REQUEST; 743 | parser->method = HTTP_HEAD; 744 | parser->index = 2; 745 | UPDATE_STATE(s_req_method); 746 | } 747 | break; 748 | 749 | case s_start_res: 750 | { 751 | parser->flags = 0; 752 | parser->content_length = ULLONG_MAX; 753 | 754 | switch (ch) { 755 | case 'H': 756 | UPDATE_STATE(s_res_H); 757 | break; 758 | 759 | case CR: 760 | case LF: 761 | break; 762 | 763 | default: 764 | SET_ERRNO(HPE_INVALID_CONSTANT); 765 | goto error; 766 | } 767 | 768 | CALLBACK_NOTIFY(message_begin); 769 | break; 770 | } 771 | 772 | case s_res_H: 773 | STRICT_CHECK(ch != 'T'); 774 | UPDATE_STATE(s_res_HT); 775 | break; 776 | 777 | case s_res_HT: 778 | STRICT_CHECK(ch != 'T'); 779 | UPDATE_STATE(s_res_HTT); 780 | break; 781 | 782 | case s_res_HTT: 783 | STRICT_CHECK(ch != 'P'); 784 | UPDATE_STATE(s_res_HTTP); 785 | break; 786 | 787 | case s_res_HTTP: 788 | STRICT_CHECK(ch != '/'); 789 | UPDATE_STATE(s_res_first_http_major); 790 | break; 791 | 792 | case s_res_first_http_major: 793 | if (UNLIKELY(ch < '0' || ch > '9')) { 794 | SET_ERRNO(HPE_INVALID_VERSION); 795 | goto error; 796 | } 797 | 798 | parser->http_major = ch - '0'; 799 | UPDATE_STATE(s_res_http_major); 800 | break; 801 | 802 | /* major HTTP version or dot */ 803 | case s_res_http_major: 804 | { 805 | if (ch == '.') { 806 | UPDATE_STATE(s_res_first_http_minor); 807 | break; 808 | } 809 | 810 | if (!IS_NUM(ch)) { 811 | SET_ERRNO(HPE_INVALID_VERSION); 812 | goto error; 813 | } 814 | 815 | parser->http_major *= 10; 816 | parser->http_major += ch - '0'; 817 | 818 | if (UNLIKELY(parser->http_major > 999)) { 819 | SET_ERRNO(HPE_INVALID_VERSION); 820 | goto error; 821 | } 822 | 823 | break; 824 | } 825 | 826 | /* first digit of minor HTTP version */ 827 | case s_res_first_http_minor: 828 | if (UNLIKELY(!IS_NUM(ch))) { 829 | SET_ERRNO(HPE_INVALID_VERSION); 830 | goto error; 831 | } 832 | 833 | parser->http_minor = ch - '0'; 834 | UPDATE_STATE(s_res_http_minor); 835 | break; 836 | 837 | /* minor HTTP version or end of request line */ 838 | case s_res_http_minor: 839 | { 840 | if (ch == ' ') { 841 | UPDATE_STATE(s_res_first_status_code); 842 | break; 843 | } 844 | 845 | if (UNLIKELY(!IS_NUM(ch))) { 846 | SET_ERRNO(HPE_INVALID_VERSION); 847 | goto error; 848 | } 849 | 850 | parser->http_minor *= 10; 851 | parser->http_minor += ch - '0'; 852 | 853 | if (UNLIKELY(parser->http_minor > 999)) { 854 | SET_ERRNO(HPE_INVALID_VERSION); 855 | goto error; 856 | } 857 | 858 | break; 859 | } 860 | 861 | case s_res_first_status_code: 862 | { 863 | if (!IS_NUM(ch)) { 864 | if (ch == ' ') { 865 | break; 866 | } 867 | 868 | SET_ERRNO(HPE_INVALID_STATUS); 869 | goto error; 870 | } 871 | parser->status_code = ch - '0'; 872 | UPDATE_STATE(s_res_status_code); 873 | break; 874 | } 875 | 876 | case s_res_status_code: 877 | { 878 | if (!IS_NUM(ch)) { 879 | switch (ch) { 880 | case ' ': 881 | UPDATE_STATE(s_res_status_start); 882 | break; 883 | case CR: 884 | UPDATE_STATE(s_res_line_almost_done); 885 | break; 886 | case LF: 887 | UPDATE_STATE(s_header_field_start); 888 | break; 889 | default: 890 | SET_ERRNO(HPE_INVALID_STATUS); 891 | goto error; 892 | } 893 | break; 894 | } 895 | 896 | parser->status_code *= 10; 897 | parser->status_code += ch - '0'; 898 | 899 | if (UNLIKELY(parser->status_code > 999)) { 900 | SET_ERRNO(HPE_INVALID_STATUS); 901 | goto error; 902 | } 903 | 904 | break; 905 | } 906 | 907 | case s_res_status_start: 908 | { 909 | if (ch == CR) { 910 | UPDATE_STATE(s_res_line_almost_done); 911 | break; 912 | } 913 | 914 | if (ch == LF) { 915 | UPDATE_STATE(s_header_field_start); 916 | break; 917 | } 918 | 919 | MARK(status); 920 | UPDATE_STATE(s_res_status); 921 | parser->index = 0; 922 | break; 923 | } 924 | 925 | case s_res_status: 926 | if (ch == CR) { 927 | UPDATE_STATE(s_res_line_almost_done); 928 | CALLBACK_DATA(status); 929 | break; 930 | } 931 | 932 | if (ch == LF) { 933 | UPDATE_STATE(s_header_field_start); 934 | CALLBACK_DATA(status); 935 | break; 936 | } 937 | 938 | break; 939 | 940 | case s_res_line_almost_done: 941 | STRICT_CHECK(ch != LF); 942 | UPDATE_STATE(s_header_field_start); 943 | break; 944 | 945 | case s_start_req: 946 | { 947 | if (ch == CR || ch == LF) 948 | break; 949 | parser->flags = 0; 950 | parser->content_length = ULLONG_MAX; 951 | 952 | if (UNLIKELY(!IS_ALPHA(ch))) { 953 | SET_ERRNO(HPE_INVALID_METHOD); 954 | goto error; 955 | } 956 | 957 | parser->method = (enum http_method) 0; 958 | parser->index = 1; 959 | switch (ch) { 960 | case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; 961 | case 'D': parser->method = HTTP_DELETE; break; 962 | case 'G': parser->method = HTTP_GET; break; 963 | case 'H': parser->method = HTTP_HEAD; break; 964 | case 'L': parser->method = HTTP_LOCK; break; 965 | case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; 966 | case 'N': parser->method = HTTP_NOTIFY; break; 967 | case 'O': parser->method = HTTP_OPTIONS; break; 968 | case 'P': parser->method = HTTP_POST; 969 | /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ 970 | break; 971 | case 'R': parser->method = HTTP_REPORT; break; 972 | case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; 973 | case 'T': parser->method = HTTP_TRACE; break; 974 | case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; 975 | default: 976 | SET_ERRNO(HPE_INVALID_METHOD); 977 | goto error; 978 | } 979 | UPDATE_STATE(s_req_method); 980 | 981 | CALLBACK_NOTIFY(message_begin); 982 | 983 | break; 984 | } 985 | 986 | case s_req_method: 987 | { 988 | const char *matcher; 989 | if (UNLIKELY(ch == '\0')) { 990 | SET_ERRNO(HPE_INVALID_METHOD); 991 | goto error; 992 | } 993 | 994 | matcher = method_strings[parser->method]; 995 | if (ch == ' ' && matcher[parser->index] == '\0') { 996 | UPDATE_STATE(s_req_spaces_before_url); 997 | } else if (ch == matcher[parser->index]) { 998 | ; /* nada */ 999 | } else if (parser->method == HTTP_CONNECT) { 1000 | if (parser->index == 1 && ch == 'H') { 1001 | parser->method = HTTP_CHECKOUT; 1002 | } else if (parser->index == 2 && ch == 'P') { 1003 | parser->method = HTTP_COPY; 1004 | } else { 1005 | SET_ERRNO(HPE_INVALID_METHOD); 1006 | goto error; 1007 | } 1008 | } else if (parser->method == HTTP_MKCOL) { 1009 | if (parser->index == 1 && ch == 'O') { 1010 | parser->method = HTTP_MOVE; 1011 | } else if (parser->index == 1 && ch == 'E') { 1012 | parser->method = HTTP_MERGE; 1013 | } else if (parser->index == 1 && ch == '-') { 1014 | parser->method = HTTP_MSEARCH; 1015 | } else if (parser->index == 2 && ch == 'A') { 1016 | parser->method = HTTP_MKACTIVITY; 1017 | } else if (parser->index == 3 && ch == 'A') { 1018 | parser->method = HTTP_MKCALENDAR; 1019 | } else { 1020 | SET_ERRNO(HPE_INVALID_METHOD); 1021 | goto error; 1022 | } 1023 | } else if (parser->method == HTTP_SUBSCRIBE) { 1024 | if (parser->index == 1 && ch == 'E') { 1025 | parser->method = HTTP_SEARCH; 1026 | } else { 1027 | SET_ERRNO(HPE_INVALID_METHOD); 1028 | goto error; 1029 | } 1030 | } else if (parser->index == 1 && parser->method == HTTP_POST) { 1031 | if (ch == 'R') { 1032 | parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ 1033 | } else if (ch == 'U') { 1034 | parser->method = HTTP_PUT; /* or HTTP_PURGE */ 1035 | } else if (ch == 'A') { 1036 | parser->method = HTTP_PATCH; 1037 | } else { 1038 | SET_ERRNO(HPE_INVALID_METHOD); 1039 | goto error; 1040 | } 1041 | } else if (parser->index == 2) { 1042 | if (parser->method == HTTP_PUT) { 1043 | if (ch == 'R') { 1044 | parser->method = HTTP_PURGE; 1045 | } else { 1046 | SET_ERRNO(HPE_INVALID_METHOD); 1047 | goto error; 1048 | } 1049 | } else if (parser->method == HTTP_UNLOCK) { 1050 | if (ch == 'S') { 1051 | parser->method = HTTP_UNSUBSCRIBE; 1052 | } else { 1053 | SET_ERRNO(HPE_INVALID_METHOD); 1054 | goto error; 1055 | } 1056 | } else { 1057 | SET_ERRNO(HPE_INVALID_METHOD); 1058 | goto error; 1059 | } 1060 | } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { 1061 | parser->method = HTTP_PROPPATCH; 1062 | } else { 1063 | SET_ERRNO(HPE_INVALID_METHOD); 1064 | goto error; 1065 | } 1066 | 1067 | ++parser->index; 1068 | break; 1069 | } 1070 | 1071 | case s_req_spaces_before_url: 1072 | { 1073 | if (ch == ' ') break; 1074 | 1075 | MARK(url); 1076 | if (parser->method == HTTP_CONNECT) { 1077 | UPDATE_STATE(s_req_server_start); 1078 | } 1079 | 1080 | UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); 1081 | if (UNLIKELY(CURRENT_STATE() == s_dead)) { 1082 | SET_ERRNO(HPE_INVALID_URL); 1083 | goto error; 1084 | } 1085 | 1086 | break; 1087 | } 1088 | 1089 | case s_req_schema: 1090 | case s_req_schema_slash: 1091 | case s_req_schema_slash_slash: 1092 | case s_req_server_start: 1093 | { 1094 | switch (ch) { 1095 | /* No whitespace allowed here */ 1096 | case ' ': 1097 | case CR: 1098 | case LF: 1099 | SET_ERRNO(HPE_INVALID_URL); 1100 | goto error; 1101 | default: 1102 | UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); 1103 | if (UNLIKELY(CURRENT_STATE() == s_dead)) { 1104 | SET_ERRNO(HPE_INVALID_URL); 1105 | goto error; 1106 | } 1107 | } 1108 | 1109 | break; 1110 | } 1111 | 1112 | case s_req_server: 1113 | case s_req_server_with_at: 1114 | case s_req_path: 1115 | case s_req_query_string_start: 1116 | case s_req_query_string: 1117 | case s_req_fragment_start: 1118 | case s_req_fragment: 1119 | { 1120 | switch (ch) { 1121 | case ' ': 1122 | UPDATE_STATE(s_req_http_start); 1123 | CALLBACK_DATA(url); 1124 | break; 1125 | case CR: 1126 | case LF: 1127 | parser->http_major = 0; 1128 | parser->http_minor = 9; 1129 | UPDATE_STATE((ch == CR) ? 1130 | s_req_line_almost_done : 1131 | s_header_field_start); 1132 | CALLBACK_DATA(url); 1133 | break; 1134 | default: 1135 | UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); 1136 | if (UNLIKELY(CURRENT_STATE() == s_dead)) { 1137 | SET_ERRNO(HPE_INVALID_URL); 1138 | goto error; 1139 | } 1140 | } 1141 | break; 1142 | } 1143 | 1144 | case s_req_http_start: 1145 | switch (ch) { 1146 | case 'H': 1147 | UPDATE_STATE(s_req_http_H); 1148 | break; 1149 | case ' ': 1150 | break; 1151 | default: 1152 | SET_ERRNO(HPE_INVALID_CONSTANT); 1153 | goto error; 1154 | } 1155 | break; 1156 | 1157 | case s_req_http_H: 1158 | STRICT_CHECK(ch != 'T'); 1159 | UPDATE_STATE(s_req_http_HT); 1160 | break; 1161 | 1162 | case s_req_http_HT: 1163 | STRICT_CHECK(ch != 'T'); 1164 | UPDATE_STATE(s_req_http_HTT); 1165 | break; 1166 | 1167 | case s_req_http_HTT: 1168 | STRICT_CHECK(ch != 'P'); 1169 | UPDATE_STATE(s_req_http_HTTP); 1170 | break; 1171 | 1172 | case s_req_http_HTTP: 1173 | STRICT_CHECK(ch != '/'); 1174 | UPDATE_STATE(s_req_first_http_major); 1175 | break; 1176 | 1177 | /* first digit of major HTTP version */ 1178 | case s_req_first_http_major: 1179 | if (UNLIKELY(ch < '1' || ch > '9')) { 1180 | SET_ERRNO(HPE_INVALID_VERSION); 1181 | goto error; 1182 | } 1183 | 1184 | parser->http_major = ch - '0'; 1185 | UPDATE_STATE(s_req_http_major); 1186 | break; 1187 | 1188 | /* major HTTP version or dot */ 1189 | case s_req_http_major: 1190 | { 1191 | if (ch == '.') { 1192 | UPDATE_STATE(s_req_first_http_minor); 1193 | break; 1194 | } 1195 | 1196 | if (UNLIKELY(!IS_NUM(ch))) { 1197 | SET_ERRNO(HPE_INVALID_VERSION); 1198 | goto error; 1199 | } 1200 | 1201 | parser->http_major *= 10; 1202 | parser->http_major += ch - '0'; 1203 | 1204 | if (UNLIKELY(parser->http_major > 999)) { 1205 | SET_ERRNO(HPE_INVALID_VERSION); 1206 | goto error; 1207 | } 1208 | 1209 | break; 1210 | } 1211 | 1212 | /* first digit of minor HTTP version */ 1213 | case s_req_first_http_minor: 1214 | if (UNLIKELY(!IS_NUM(ch))) { 1215 | SET_ERRNO(HPE_INVALID_VERSION); 1216 | goto error; 1217 | } 1218 | 1219 | parser->http_minor = ch - '0'; 1220 | UPDATE_STATE(s_req_http_minor); 1221 | break; 1222 | 1223 | /* minor HTTP version or end of request line */ 1224 | case s_req_http_minor: 1225 | { 1226 | if (ch == CR) { 1227 | UPDATE_STATE(s_req_line_almost_done); 1228 | break; 1229 | } 1230 | 1231 | if (ch == LF) { 1232 | UPDATE_STATE(s_header_field_start); 1233 | break; 1234 | } 1235 | 1236 | /* XXX allow spaces after digit? */ 1237 | 1238 | if (UNLIKELY(!IS_NUM(ch))) { 1239 | SET_ERRNO(HPE_INVALID_VERSION); 1240 | goto error; 1241 | } 1242 | 1243 | parser->http_minor *= 10; 1244 | parser->http_minor += ch - '0'; 1245 | 1246 | if (UNLIKELY(parser->http_minor > 999)) { 1247 | SET_ERRNO(HPE_INVALID_VERSION); 1248 | goto error; 1249 | } 1250 | 1251 | break; 1252 | } 1253 | 1254 | /* end of request line */ 1255 | case s_req_line_almost_done: 1256 | { 1257 | if (UNLIKELY(ch != LF)) { 1258 | SET_ERRNO(HPE_LF_EXPECTED); 1259 | goto error; 1260 | } 1261 | 1262 | UPDATE_STATE(s_header_field_start); 1263 | break; 1264 | } 1265 | 1266 | case s_header_field_start: 1267 | { 1268 | if (ch == CR) { 1269 | UPDATE_STATE(s_headers_almost_done); 1270 | break; 1271 | } 1272 | 1273 | if (ch == LF) { 1274 | /* they might be just sending \n instead of \r\n so this would be 1275 | * the second \n to denote the end of headers*/ 1276 | UPDATE_STATE(s_headers_almost_done); 1277 | REEXECUTE(); 1278 | } 1279 | 1280 | c = TOKEN(ch); 1281 | 1282 | if (UNLIKELY(!c)) { 1283 | SET_ERRNO(HPE_INVALID_HEADER_TOKEN); 1284 | goto error; 1285 | } 1286 | 1287 | MARK(header_field); 1288 | 1289 | parser->index = 0; 1290 | UPDATE_STATE(s_header_field); 1291 | 1292 | switch (c) { 1293 | case 'c': 1294 | parser->header_state = h_C; 1295 | break; 1296 | 1297 | case 'p': 1298 | parser->header_state = h_matching_proxy_connection; 1299 | break; 1300 | 1301 | case 't': 1302 | parser->header_state = h_matching_transfer_encoding; 1303 | break; 1304 | 1305 | case 'u': 1306 | parser->header_state = h_matching_upgrade; 1307 | break; 1308 | 1309 | default: 1310 | parser->header_state = h_general; 1311 | break; 1312 | } 1313 | break; 1314 | } 1315 | 1316 | case s_header_field: 1317 | { 1318 | const char* start = p; 1319 | for (; p != data + len; p++) { 1320 | ch = *p; 1321 | c = TOKEN(ch); 1322 | 1323 | if (!c) 1324 | break; 1325 | 1326 | switch (parser->header_state) { 1327 | case h_general: 1328 | break; 1329 | 1330 | case h_C: 1331 | parser->index++; 1332 | parser->header_state = (c == 'o' ? h_CO : h_general); 1333 | break; 1334 | 1335 | case h_CO: 1336 | parser->index++; 1337 | parser->header_state = (c == 'n' ? h_CON : h_general); 1338 | break; 1339 | 1340 | case h_CON: 1341 | parser->index++; 1342 | switch (c) { 1343 | case 'n': 1344 | parser->header_state = h_matching_connection; 1345 | break; 1346 | case 't': 1347 | parser->header_state = h_matching_content_length; 1348 | break; 1349 | default: 1350 | parser->header_state = h_general; 1351 | break; 1352 | } 1353 | break; 1354 | 1355 | /* connection */ 1356 | 1357 | case h_matching_connection: 1358 | parser->index++; 1359 | if (parser->index > sizeof(CONNECTION)-1 1360 | || c != CONNECTION[parser->index]) { 1361 | parser->header_state = h_general; 1362 | } else if (parser->index == sizeof(CONNECTION)-2) { 1363 | parser->header_state = h_connection; 1364 | } 1365 | break; 1366 | 1367 | /* proxy-connection */ 1368 | 1369 | case h_matching_proxy_connection: 1370 | parser->index++; 1371 | if (parser->index > sizeof(PROXY_CONNECTION)-1 1372 | || c != PROXY_CONNECTION[parser->index]) { 1373 | parser->header_state = h_general; 1374 | } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { 1375 | parser->header_state = h_connection; 1376 | } 1377 | break; 1378 | 1379 | /* content-length */ 1380 | 1381 | case h_matching_content_length: 1382 | parser->index++; 1383 | if (parser->index > sizeof(CONTENT_LENGTH)-1 1384 | || c != CONTENT_LENGTH[parser->index]) { 1385 | parser->header_state = h_general; 1386 | } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { 1387 | parser->header_state = h_content_length; 1388 | } 1389 | break; 1390 | 1391 | /* transfer-encoding */ 1392 | 1393 | case h_matching_transfer_encoding: 1394 | parser->index++; 1395 | if (parser->index > sizeof(TRANSFER_ENCODING)-1 1396 | || c != TRANSFER_ENCODING[parser->index]) { 1397 | parser->header_state = h_general; 1398 | } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { 1399 | parser->header_state = h_transfer_encoding; 1400 | } 1401 | break; 1402 | 1403 | /* upgrade */ 1404 | 1405 | case h_matching_upgrade: 1406 | parser->index++; 1407 | if (parser->index > sizeof(UPGRADE)-1 1408 | || c != UPGRADE[parser->index]) { 1409 | parser->header_state = h_general; 1410 | } else if (parser->index == sizeof(UPGRADE)-2) { 1411 | parser->header_state = h_upgrade; 1412 | } 1413 | break; 1414 | 1415 | case h_connection: 1416 | case h_content_length: 1417 | case h_transfer_encoding: 1418 | case h_upgrade: 1419 | if (ch != ' ') parser->header_state = h_general; 1420 | break; 1421 | 1422 | default: 1423 | assert(0 && "Unknown header_state"); 1424 | break; 1425 | } 1426 | } 1427 | 1428 | COUNT_HEADER_SIZE(p - start); 1429 | 1430 | if (p == data + len) { 1431 | --p; 1432 | break; 1433 | } 1434 | 1435 | if (ch == ':') { 1436 | UPDATE_STATE(s_header_value_discard_ws); 1437 | CALLBACK_DATA(header_field); 1438 | break; 1439 | } 1440 | 1441 | SET_ERRNO(HPE_INVALID_HEADER_TOKEN); 1442 | goto error; 1443 | } 1444 | 1445 | case s_header_value_discard_ws: 1446 | if (ch == ' ' || ch == '\t') break; 1447 | 1448 | if (ch == CR) { 1449 | UPDATE_STATE(s_header_value_discard_ws_almost_done); 1450 | break; 1451 | } 1452 | 1453 | if (ch == LF) { 1454 | UPDATE_STATE(s_header_value_discard_lws); 1455 | break; 1456 | } 1457 | 1458 | /* FALLTHROUGH */ 1459 | 1460 | case s_header_value_start: 1461 | { 1462 | MARK(header_value); 1463 | 1464 | UPDATE_STATE(s_header_value); 1465 | parser->index = 0; 1466 | 1467 | c = LOWER(ch); 1468 | 1469 | switch (parser->header_state) { 1470 | case h_upgrade: 1471 | parser->flags |= F_UPGRADE; 1472 | parser->header_state = h_general; 1473 | break; 1474 | 1475 | case h_transfer_encoding: 1476 | /* looking for 'Transfer-Encoding: chunked' */ 1477 | if ('c' == c) { 1478 | parser->header_state = h_matching_transfer_encoding_chunked; 1479 | } else { 1480 | parser->header_state = h_general; 1481 | } 1482 | break; 1483 | 1484 | case h_content_length: 1485 | if (UNLIKELY(!IS_NUM(ch))) { 1486 | SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); 1487 | goto error; 1488 | } 1489 | 1490 | parser->content_length = ch - '0'; 1491 | break; 1492 | 1493 | case h_connection: 1494 | /* looking for 'Connection: keep-alive' */ 1495 | if (c == 'k') { 1496 | parser->header_state = h_matching_connection_keep_alive; 1497 | /* looking for 'Connection: close' */ 1498 | } else if (c == 'c') { 1499 | parser->header_state = h_matching_connection_close; 1500 | } else if (c == 'u') { 1501 | parser->header_state = h_matching_connection_upgrade; 1502 | } else { 1503 | parser->header_state = h_matching_connection_token; 1504 | } 1505 | break; 1506 | 1507 | /* Multi-value `Connection` header */ 1508 | case h_matching_connection_token_start: 1509 | break; 1510 | 1511 | default: 1512 | parser->header_state = h_general; 1513 | break; 1514 | } 1515 | break; 1516 | } 1517 | 1518 | case s_header_value: 1519 | { 1520 | const char* start = p; 1521 | enum header_states h_state = (enum header_states) parser->header_state; 1522 | for (; p != data + len; p++) { 1523 | ch = *p; 1524 | if (ch == CR) { 1525 | UPDATE_STATE(s_header_almost_done); 1526 | parser->header_state = h_state; 1527 | CALLBACK_DATA(header_value); 1528 | break; 1529 | } 1530 | 1531 | if (ch == LF) { 1532 | UPDATE_STATE(s_header_almost_done); 1533 | COUNT_HEADER_SIZE(p - start); 1534 | parser->header_state = h_state; 1535 | CALLBACK_DATA_NOADVANCE(header_value); 1536 | REEXECUTE(); 1537 | } 1538 | 1539 | c = LOWER(ch); 1540 | 1541 | switch (h_state) { 1542 | case h_general: 1543 | { 1544 | const char* p_cr; 1545 | const char* p_lf; 1546 | size_t limit = data + len - p; 1547 | 1548 | limit = MIN(limit, HTTP_MAX_HEADER_SIZE); 1549 | 1550 | p_cr = (const char*) memchr(p, CR, limit); 1551 | p_lf = (const char*) memchr(p, LF, limit); 1552 | if (p_cr != NULL) { 1553 | if (p_lf != NULL && p_cr >= p_lf) 1554 | p = p_lf; 1555 | else 1556 | p = p_cr; 1557 | } else if (UNLIKELY(p_lf != NULL)) { 1558 | p = p_lf; 1559 | } else { 1560 | p = data + len; 1561 | } 1562 | --p; 1563 | 1564 | break; 1565 | } 1566 | 1567 | case h_connection: 1568 | case h_transfer_encoding: 1569 | assert(0 && "Shouldn't get here."); 1570 | break; 1571 | 1572 | case h_content_length: 1573 | { 1574 | uint64_t t; 1575 | 1576 | if (ch == ' ') break; 1577 | 1578 | if (UNLIKELY(!IS_NUM(ch))) { 1579 | SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); 1580 | parser->header_state = h_state; 1581 | goto error; 1582 | } 1583 | 1584 | t = parser->content_length; 1585 | t *= 10; 1586 | t += ch - '0'; 1587 | 1588 | /* Overflow? Test against a conservative limit for simplicity. */ 1589 | if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { 1590 | SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); 1591 | parser->header_state = h_state; 1592 | goto error; 1593 | } 1594 | 1595 | parser->content_length = t; 1596 | break; 1597 | } 1598 | 1599 | /* Transfer-Encoding: chunked */ 1600 | case h_matching_transfer_encoding_chunked: 1601 | parser->index++; 1602 | if (parser->index > sizeof(CHUNKED)-1 1603 | || c != CHUNKED[parser->index]) { 1604 | h_state = h_general; 1605 | } else if (parser->index == sizeof(CHUNKED)-2) { 1606 | h_state = h_transfer_encoding_chunked; 1607 | } 1608 | break; 1609 | 1610 | case h_matching_connection_token_start: 1611 | /* looking for 'Connection: keep-alive' */ 1612 | if (c == 'k') { 1613 | h_state = h_matching_connection_keep_alive; 1614 | /* looking for 'Connection: close' */ 1615 | } else if (c == 'c') { 1616 | h_state = h_matching_connection_close; 1617 | } else if (c == 'u') { 1618 | h_state = h_matching_connection_upgrade; 1619 | } else if (STRICT_TOKEN(c)) { 1620 | h_state = h_matching_connection_token; 1621 | } else if (c == ' ' || c == '\t') { 1622 | /* Skip lws */ 1623 | } else { 1624 | h_state = h_general; 1625 | } 1626 | break; 1627 | 1628 | /* looking for 'Connection: keep-alive' */ 1629 | case h_matching_connection_keep_alive: 1630 | parser->index++; 1631 | if (parser->index > sizeof(KEEP_ALIVE)-1 1632 | || c != KEEP_ALIVE[parser->index]) { 1633 | h_state = h_matching_connection_token; 1634 | } else if (parser->index == sizeof(KEEP_ALIVE)-2) { 1635 | h_state = h_connection_keep_alive; 1636 | } 1637 | break; 1638 | 1639 | /* looking for 'Connection: close' */ 1640 | case h_matching_connection_close: 1641 | parser->index++; 1642 | if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { 1643 | h_state = h_matching_connection_token; 1644 | } else if (parser->index == sizeof(CLOSE)-2) { 1645 | h_state = h_connection_close; 1646 | } 1647 | break; 1648 | 1649 | /* looking for 'Connection: upgrade' */ 1650 | case h_matching_connection_upgrade: 1651 | parser->index++; 1652 | if (parser->index > sizeof(UPGRADE) - 1 || 1653 | c != UPGRADE[parser->index]) { 1654 | h_state = h_matching_connection_token; 1655 | } else if (parser->index == sizeof(UPGRADE)-2) { 1656 | h_state = h_connection_upgrade; 1657 | } 1658 | break; 1659 | 1660 | case h_matching_connection_token: 1661 | if (ch == ',') { 1662 | h_state = h_matching_connection_token_start; 1663 | parser->index = 0; 1664 | } 1665 | break; 1666 | 1667 | case h_transfer_encoding_chunked: 1668 | if (ch != ' ') h_state = h_general; 1669 | break; 1670 | 1671 | case h_connection_keep_alive: 1672 | case h_connection_close: 1673 | case h_connection_upgrade: 1674 | if (ch == ',') { 1675 | if (h_state == h_connection_keep_alive) { 1676 | parser->flags |= F_CONNECTION_KEEP_ALIVE; 1677 | } else if (h_state == h_connection_close) { 1678 | parser->flags |= F_CONNECTION_CLOSE; 1679 | } else if (h_state == h_connection_upgrade) { 1680 | parser->flags |= F_CONNECTION_UPGRADE; 1681 | } 1682 | h_state = h_matching_connection_token_start; 1683 | parser->index = 0; 1684 | } else if (ch != ' ') { 1685 | h_state = h_matching_connection_token; 1686 | } 1687 | break; 1688 | 1689 | default: 1690 | UPDATE_STATE(s_header_value); 1691 | h_state = h_general; 1692 | break; 1693 | } 1694 | } 1695 | parser->header_state = h_state; 1696 | 1697 | COUNT_HEADER_SIZE(p - start); 1698 | 1699 | if (p == data + len) 1700 | --p; 1701 | break; 1702 | } 1703 | 1704 | case s_header_almost_done: 1705 | { 1706 | STRICT_CHECK(ch != LF); 1707 | 1708 | UPDATE_STATE(s_header_value_lws); 1709 | break; 1710 | } 1711 | 1712 | case s_header_value_lws: 1713 | { 1714 | if (ch == ' ' || ch == '\t') { 1715 | UPDATE_STATE(s_header_value_start); 1716 | REEXECUTE(); 1717 | } 1718 | 1719 | /* finished the header */ 1720 | switch (parser->header_state) { 1721 | case h_connection_keep_alive: 1722 | parser->flags |= F_CONNECTION_KEEP_ALIVE; 1723 | break; 1724 | case h_connection_close: 1725 | parser->flags |= F_CONNECTION_CLOSE; 1726 | break; 1727 | case h_transfer_encoding_chunked: 1728 | parser->flags |= F_CHUNKED; 1729 | break; 1730 | case h_connection_upgrade: 1731 | parser->flags |= F_CONNECTION_UPGRADE; 1732 | break; 1733 | default: 1734 | break; 1735 | } 1736 | 1737 | UPDATE_STATE(s_header_field_start); 1738 | REEXECUTE(); 1739 | } 1740 | 1741 | case s_header_value_discard_ws_almost_done: 1742 | { 1743 | STRICT_CHECK(ch != LF); 1744 | UPDATE_STATE(s_header_value_discard_lws); 1745 | break; 1746 | } 1747 | 1748 | case s_header_value_discard_lws: 1749 | { 1750 | if (ch == ' ' || ch == '\t') { 1751 | UPDATE_STATE(s_header_value_discard_ws); 1752 | break; 1753 | } else { 1754 | switch (parser->header_state) { 1755 | case h_connection_keep_alive: 1756 | parser->flags |= F_CONNECTION_KEEP_ALIVE; 1757 | break; 1758 | case h_connection_close: 1759 | parser->flags |= F_CONNECTION_CLOSE; 1760 | break; 1761 | case h_connection_upgrade: 1762 | parser->flags |= F_CONNECTION_UPGRADE; 1763 | break; 1764 | case h_transfer_encoding_chunked: 1765 | parser->flags |= F_CHUNKED; 1766 | break; 1767 | default: 1768 | break; 1769 | } 1770 | 1771 | /* header value was empty */ 1772 | MARK(header_value); 1773 | UPDATE_STATE(s_header_field_start); 1774 | CALLBACK_DATA_NOADVANCE(header_value); 1775 | REEXECUTE(); 1776 | } 1777 | } 1778 | 1779 | case s_headers_almost_done: 1780 | { 1781 | STRICT_CHECK(ch != LF); 1782 | 1783 | if (parser->flags & F_TRAILING) { 1784 | /* End of a chunked request */ 1785 | UPDATE_STATE(s_message_done); 1786 | CALLBACK_NOTIFY_NOADVANCE(chunk_complete); 1787 | REEXECUTE(); 1788 | } 1789 | 1790 | UPDATE_STATE(s_headers_done); 1791 | 1792 | /* Set this here so that on_headers_complete() callbacks can see it */ 1793 | parser->upgrade = 1794 | ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) == 1795 | (F_UPGRADE | F_CONNECTION_UPGRADE) || 1796 | parser->method == HTTP_CONNECT); 1797 | 1798 | /* Here we call the headers_complete callback. This is somewhat 1799 | * different than other callbacks because if the user returns 1, we 1800 | * will interpret that as saying that this message has no body. This 1801 | * is needed for the annoying case of recieving a response to a HEAD 1802 | * request. 1803 | * 1804 | * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so 1805 | * we have to simulate it by handling a change in errno below. 1806 | */ 1807 | if (settings->on_headers_complete) { 1808 | switch (settings->on_headers_complete(parser)) { 1809 | case 0: 1810 | break; 1811 | 1812 | case 1: 1813 | parser->flags |= F_SKIPBODY; 1814 | break; 1815 | 1816 | default: 1817 | SET_ERRNO(HPE_CB_headers_complete); 1818 | RETURN(p - data); /* Error */ 1819 | } 1820 | } 1821 | 1822 | if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { 1823 | RETURN(p - data); 1824 | } 1825 | 1826 | REEXECUTE(); 1827 | } 1828 | 1829 | case s_headers_done: 1830 | { 1831 | STRICT_CHECK(ch != LF); 1832 | 1833 | parser->nread = 0; 1834 | 1835 | int hasBody = parser->flags & F_CHUNKED || 1836 | (parser->content_length > 0 && parser->content_length != ULLONG_MAX); 1837 | if (parser->upgrade && (parser->method == HTTP_CONNECT || 1838 | (parser->flags & F_SKIPBODY) || !hasBody)) { 1839 | /* Exit, the rest of the message is in a different protocol. */ 1840 | UPDATE_STATE(NEW_MESSAGE()); 1841 | CALLBACK_NOTIFY(message_complete); 1842 | RETURN((p - data) + 1); 1843 | } 1844 | 1845 | if (parser->flags & F_SKIPBODY) { 1846 | UPDATE_STATE(NEW_MESSAGE()); 1847 | CALLBACK_NOTIFY(message_complete); 1848 | } else if (parser->flags & F_CHUNKED) { 1849 | /* chunked encoding - ignore Content-Length header */ 1850 | UPDATE_STATE(s_chunk_size_start); 1851 | } else { 1852 | if (parser->content_length == 0) { 1853 | /* Content-Length header given but zero: Content-Length: 0\r\n */ 1854 | UPDATE_STATE(NEW_MESSAGE()); 1855 | CALLBACK_NOTIFY(message_complete); 1856 | } else if (parser->content_length != ULLONG_MAX) { 1857 | /* Content-Length header given and non-zero */ 1858 | UPDATE_STATE(s_body_identity); 1859 | } else { 1860 | if (parser->type == HTTP_REQUEST || 1861 | !http_message_needs_eof(parser)) { 1862 | /* Assume content-length 0 - read the next */ 1863 | UPDATE_STATE(NEW_MESSAGE()); 1864 | CALLBACK_NOTIFY(message_complete); 1865 | } else { 1866 | /* Read body until EOF */ 1867 | UPDATE_STATE(s_body_identity_eof); 1868 | } 1869 | } 1870 | } 1871 | 1872 | break; 1873 | } 1874 | 1875 | case s_body_identity: 1876 | { 1877 | uint64_t to_read = MIN(parser->content_length, 1878 | (uint64_t) ((data + len) - p)); 1879 | 1880 | assert(parser->content_length != 0 1881 | && parser->content_length != ULLONG_MAX); 1882 | 1883 | /* The difference between advancing content_length and p is because 1884 | * the latter will automaticaly advance on the next loop iteration. 1885 | * Further, if content_length ends up at 0, we want to see the last 1886 | * byte again for our message complete callback. 1887 | */ 1888 | MARK(body); 1889 | parser->content_length -= to_read; 1890 | p += to_read - 1; 1891 | 1892 | if (parser->content_length == 0) { 1893 | UPDATE_STATE(s_message_done); 1894 | 1895 | /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. 1896 | * 1897 | * The alternative to doing this is to wait for the next byte to 1898 | * trigger the data callback, just as in every other case. The 1899 | * problem with this is that this makes it difficult for the test 1900 | * harness to distinguish between complete-on-EOF and 1901 | * complete-on-length. It's not clear that this distinction is 1902 | * important for applications, but let's keep it for now. 1903 | */ 1904 | CALLBACK_DATA_(body, p - body_mark + 1, p - data); 1905 | REEXECUTE(); 1906 | } 1907 | 1908 | break; 1909 | } 1910 | 1911 | /* read until EOF */ 1912 | case s_body_identity_eof: 1913 | MARK(body); 1914 | p = data + len - 1; 1915 | 1916 | break; 1917 | 1918 | case s_message_done: 1919 | UPDATE_STATE(NEW_MESSAGE()); 1920 | CALLBACK_NOTIFY(message_complete); 1921 | if (parser->upgrade) { 1922 | /* Exit, the rest of the message is in a different protocol. */ 1923 | RETURN((p - data) + 1); 1924 | } 1925 | break; 1926 | 1927 | case s_chunk_size_start: 1928 | { 1929 | assert(parser->nread == 1); 1930 | assert(parser->flags & F_CHUNKED); 1931 | 1932 | unhex_val = unhex[(unsigned char)ch]; 1933 | if (UNLIKELY(unhex_val == -1)) { 1934 | SET_ERRNO(HPE_INVALID_CHUNK_SIZE); 1935 | goto error; 1936 | } 1937 | 1938 | parser->content_length = unhex_val; 1939 | UPDATE_STATE(s_chunk_size); 1940 | break; 1941 | } 1942 | 1943 | case s_chunk_size: 1944 | { 1945 | uint64_t t; 1946 | 1947 | assert(parser->flags & F_CHUNKED); 1948 | 1949 | if (ch == CR) { 1950 | UPDATE_STATE(s_chunk_size_almost_done); 1951 | break; 1952 | } 1953 | 1954 | unhex_val = unhex[(unsigned char)ch]; 1955 | 1956 | if (unhex_val == -1) { 1957 | if (ch == ';' || ch == ' ') { 1958 | UPDATE_STATE(s_chunk_parameters); 1959 | break; 1960 | } 1961 | 1962 | SET_ERRNO(HPE_INVALID_CHUNK_SIZE); 1963 | goto error; 1964 | } 1965 | 1966 | t = parser->content_length; 1967 | t *= 16; 1968 | t += unhex_val; 1969 | 1970 | /* Overflow? Test against a conservative limit for simplicity. */ 1971 | if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { 1972 | SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); 1973 | goto error; 1974 | } 1975 | 1976 | parser->content_length = t; 1977 | break; 1978 | } 1979 | 1980 | case s_chunk_parameters: 1981 | { 1982 | assert(parser->flags & F_CHUNKED); 1983 | /* just ignore this shit. TODO check for overflow */ 1984 | if (ch == CR) { 1985 | UPDATE_STATE(s_chunk_size_almost_done); 1986 | break; 1987 | } 1988 | break; 1989 | } 1990 | 1991 | case s_chunk_size_almost_done: 1992 | { 1993 | assert(parser->flags & F_CHUNKED); 1994 | STRICT_CHECK(ch != LF); 1995 | 1996 | parser->nread = 0; 1997 | 1998 | if (parser->content_length == 0) { 1999 | parser->flags |= F_TRAILING; 2000 | UPDATE_STATE(s_header_field_start); 2001 | } else { 2002 | UPDATE_STATE(s_chunk_data); 2003 | } 2004 | CALLBACK_NOTIFY(chunk_header); 2005 | break; 2006 | } 2007 | 2008 | case s_chunk_data: 2009 | { 2010 | uint64_t to_read = MIN(parser->content_length, 2011 | (uint64_t) ((data + len) - p)); 2012 | 2013 | assert(parser->flags & F_CHUNKED); 2014 | assert(parser->content_length != 0 2015 | && parser->content_length != ULLONG_MAX); 2016 | 2017 | /* See the explanation in s_body_identity for why the content 2018 | * length and data pointers are managed this way. 2019 | */ 2020 | MARK(body); 2021 | parser->content_length -= to_read; 2022 | p += to_read - 1; 2023 | 2024 | if (parser->content_length == 0) { 2025 | UPDATE_STATE(s_chunk_data_almost_done); 2026 | } 2027 | 2028 | break; 2029 | } 2030 | 2031 | case s_chunk_data_almost_done: 2032 | assert(parser->flags & F_CHUNKED); 2033 | assert(parser->content_length == 0); 2034 | STRICT_CHECK(ch != CR); 2035 | UPDATE_STATE(s_chunk_data_done); 2036 | CALLBACK_DATA(body); 2037 | break; 2038 | 2039 | case s_chunk_data_done: 2040 | assert(parser->flags & F_CHUNKED); 2041 | STRICT_CHECK(ch != LF); 2042 | parser->nread = 0; 2043 | UPDATE_STATE(s_chunk_size_start); 2044 | CALLBACK_NOTIFY(chunk_complete); 2045 | break; 2046 | 2047 | default: 2048 | assert(0 && "unhandled state"); 2049 | SET_ERRNO(HPE_INVALID_INTERNAL_STATE); 2050 | goto error; 2051 | } 2052 | } 2053 | 2054 | /* Run callbacks for any marks that we have leftover after we ran our of 2055 | * bytes. There should be at most one of these set, so it's OK to invoke 2056 | * them in series (unset marks will not result in callbacks). 2057 | * 2058 | * We use the NOADVANCE() variety of callbacks here because 'p' has already 2059 | * overflowed 'data' and this allows us to correct for the off-by-one that 2060 | * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' 2061 | * value that's in-bounds). 2062 | */ 2063 | 2064 | assert(((header_field_mark ? 1 : 0) + 2065 | (header_value_mark ? 1 : 0) + 2066 | (url_mark ? 1 : 0) + 2067 | (body_mark ? 1 : 0) + 2068 | (status_mark ? 1 : 0)) <= 1); 2069 | 2070 | CALLBACK_DATA_NOADVANCE(header_field); 2071 | CALLBACK_DATA_NOADVANCE(header_value); 2072 | CALLBACK_DATA_NOADVANCE(url); 2073 | CALLBACK_DATA_NOADVANCE(body); 2074 | CALLBACK_DATA_NOADVANCE(status); 2075 | 2076 | RETURN(len); 2077 | 2078 | error: 2079 | if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { 2080 | SET_ERRNO(HPE_UNKNOWN); 2081 | } 2082 | 2083 | RETURN(p - data); 2084 | } 2085 | 2086 | 2087 | /* Does the parser need to see an EOF to find the end of the message? */ 2088 | int 2089 | http_message_needs_eof (const http_parser *parser) 2090 | { 2091 | if (parser->type == HTTP_REQUEST) { 2092 | return 0; 2093 | } 2094 | 2095 | /* See RFC 2616 section 4.4 */ 2096 | if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ 2097 | parser->status_code == 204 || /* No Content */ 2098 | parser->status_code == 304 || /* Not Modified */ 2099 | parser->flags & F_SKIPBODY) { /* response to a HEAD request */ 2100 | return 0; 2101 | } 2102 | 2103 | if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { 2104 | return 0; 2105 | } 2106 | 2107 | return 1; 2108 | } 2109 | 2110 | 2111 | int 2112 | http_should_keep_alive (const http_parser *parser) 2113 | { 2114 | if (parser->http_major > 0 && parser->http_minor > 0) { 2115 | /* HTTP/1.1 */ 2116 | if (parser->flags & F_CONNECTION_CLOSE) { 2117 | return 0; 2118 | } 2119 | } else { 2120 | /* HTTP/1.0 or earlier */ 2121 | if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { 2122 | return 0; 2123 | } 2124 | } 2125 | 2126 | return !http_message_needs_eof(parser); 2127 | } 2128 | 2129 | 2130 | const char * 2131 | http_method_str (enum http_method m) 2132 | { 2133 | return ELEM_AT(method_strings, m, ""); 2134 | } 2135 | 2136 | 2137 | void 2138 | http_parser_init (http_parser *parser, enum http_parser_type t) 2139 | { 2140 | void *data = parser->data; /* preserve application data */ 2141 | memset(parser, 0, sizeof(*parser)); 2142 | parser->data = data; 2143 | parser->type = t; 2144 | parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); 2145 | parser->http_errno = HPE_OK; 2146 | } 2147 | 2148 | void 2149 | http_parser_settings_init(http_parser_settings *settings) 2150 | { 2151 | memset(settings, 0, sizeof(*settings)); 2152 | } 2153 | 2154 | const char * 2155 | http_errno_name(enum http_errno err) { 2156 | assert(((size_t) err) < 2157 | (sizeof(http_strerror_tab) / sizeof(http_strerror_tab[0]))); 2158 | return http_strerror_tab[err].name; 2159 | } 2160 | 2161 | const char * 2162 | http_errno_description(enum http_errno err) { 2163 | assert(((size_t) err) < 2164 | (sizeof(http_strerror_tab) / sizeof(http_strerror_tab[0]))); 2165 | return http_strerror_tab[err].description; 2166 | } 2167 | 2168 | static enum http_host_state 2169 | http_parse_host_char(enum http_host_state s, const char ch) { 2170 | switch(s) { 2171 | case s_http_userinfo: 2172 | case s_http_userinfo_start: 2173 | if (ch == '@') { 2174 | return s_http_host_start; 2175 | } 2176 | 2177 | if (IS_USERINFO_CHAR(ch)) { 2178 | return s_http_userinfo; 2179 | } 2180 | break; 2181 | 2182 | case s_http_host_start: 2183 | if (ch == '[') { 2184 | return s_http_host_v6_start; 2185 | } 2186 | 2187 | if (IS_HOST_CHAR(ch)) { 2188 | return s_http_host; 2189 | } 2190 | 2191 | break; 2192 | 2193 | case s_http_host: 2194 | if (IS_HOST_CHAR(ch)) { 2195 | return s_http_host; 2196 | } 2197 | 2198 | /* FALLTHROUGH */ 2199 | case s_http_host_v6_end: 2200 | if (ch == ':') { 2201 | return s_http_host_port_start; 2202 | } 2203 | 2204 | break; 2205 | 2206 | case s_http_host_v6: 2207 | if (ch == ']') { 2208 | return s_http_host_v6_end; 2209 | } 2210 | 2211 | /* FALLTHROUGH */ 2212 | case s_http_host_v6_start: 2213 | if (IS_HEX(ch) || ch == ':' || ch == '.') { 2214 | return s_http_host_v6; 2215 | } 2216 | 2217 | break; 2218 | 2219 | case s_http_host_port: 2220 | case s_http_host_port_start: 2221 | if (IS_NUM(ch)) { 2222 | return s_http_host_port; 2223 | } 2224 | 2225 | break; 2226 | 2227 | default: 2228 | break; 2229 | } 2230 | return s_http_host_dead; 2231 | } 2232 | 2233 | static int 2234 | http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { 2235 | enum http_host_state s; 2236 | 2237 | const char *p; 2238 | size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; 2239 | 2240 | u->field_data[UF_HOST].len = 0; 2241 | 2242 | s = found_at ? s_http_userinfo_start : s_http_host_start; 2243 | 2244 | for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { 2245 | enum http_host_state new_s = http_parse_host_char(s, *p); 2246 | 2247 | if (new_s == s_http_host_dead) { 2248 | return 1; 2249 | } 2250 | 2251 | switch(new_s) { 2252 | case s_http_host: 2253 | if (s != s_http_host) { 2254 | u->field_data[UF_HOST].off = p - buf; 2255 | } 2256 | u->field_data[UF_HOST].len++; 2257 | break; 2258 | 2259 | case s_http_host_v6: 2260 | if (s != s_http_host_v6) { 2261 | u->field_data[UF_HOST].off = p - buf; 2262 | } 2263 | u->field_data[UF_HOST].len++; 2264 | break; 2265 | 2266 | case s_http_host_port: 2267 | if (s != s_http_host_port) { 2268 | u->field_data[UF_PORT].off = p - buf; 2269 | u->field_data[UF_PORT].len = 0; 2270 | u->field_set |= (1 << UF_PORT); 2271 | } 2272 | u->field_data[UF_PORT].len++; 2273 | break; 2274 | 2275 | case s_http_userinfo: 2276 | if (s != s_http_userinfo) { 2277 | u->field_data[UF_USERINFO].off = p - buf ; 2278 | u->field_data[UF_USERINFO].len = 0; 2279 | u->field_set |= (1 << UF_USERINFO); 2280 | } 2281 | u->field_data[UF_USERINFO].len++; 2282 | break; 2283 | 2284 | default: 2285 | break; 2286 | } 2287 | s = new_s; 2288 | } 2289 | 2290 | /* Make sure we don't end somewhere unexpected */ 2291 | switch (s) { 2292 | case s_http_host_start: 2293 | case s_http_host_v6_start: 2294 | case s_http_host_v6: 2295 | case s_http_host_port_start: 2296 | case s_http_userinfo: 2297 | case s_http_userinfo_start: 2298 | return 1; 2299 | default: 2300 | break; 2301 | } 2302 | 2303 | return 0; 2304 | } 2305 | 2306 | int 2307 | http_parser_parse_url(const char *buf, size_t buflen, int is_connect, 2308 | struct http_parser_url *u) 2309 | { 2310 | enum state s; 2311 | const char *p; 2312 | enum http_parser_url_fields uf, old_uf; 2313 | int found_at = 0; 2314 | 2315 | u->port = u->field_set = 0; 2316 | s = is_connect ? s_req_server_start : s_req_spaces_before_url; 2317 | old_uf = UF_MAX; 2318 | 2319 | for (p = buf; p < buf + buflen; p++) { 2320 | s = parse_url_char(s, *p); 2321 | 2322 | /* Figure out the next field that we're operating on */ 2323 | switch (s) { 2324 | case s_dead: 2325 | return 1; 2326 | 2327 | /* Skip delimeters */ 2328 | case s_req_schema_slash: 2329 | case s_req_schema_slash_slash: 2330 | case s_req_server_start: 2331 | case s_req_query_string_start: 2332 | case s_req_fragment_start: 2333 | continue; 2334 | 2335 | case s_req_schema: 2336 | uf = UF_SCHEMA; 2337 | break; 2338 | 2339 | case s_req_server_with_at: 2340 | found_at = 1; 2341 | 2342 | /* FALLTROUGH */ 2343 | case s_req_server: 2344 | uf = UF_HOST; 2345 | break; 2346 | 2347 | case s_req_path: 2348 | uf = UF_PATH; 2349 | break; 2350 | 2351 | case s_req_query_string: 2352 | uf = UF_QUERY; 2353 | break; 2354 | 2355 | case s_req_fragment: 2356 | uf = UF_FRAGMENT; 2357 | break; 2358 | 2359 | default: 2360 | assert(!"Unexpected state"); 2361 | return 1; 2362 | } 2363 | 2364 | /* Nothing's changed; soldier on */ 2365 | if (uf == old_uf) { 2366 | u->field_data[uf].len++; 2367 | continue; 2368 | } 2369 | 2370 | u->field_data[uf].off = p - buf; 2371 | u->field_data[uf].len = 1; 2372 | 2373 | u->field_set |= (1 << uf); 2374 | old_uf = uf; 2375 | } 2376 | 2377 | /* host must be present if there is a schema */ 2378 | /* parsing http:///toto will fail */ 2379 | if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) { 2380 | if (http_parse_host(buf, u, found_at) != 0) { 2381 | return 1; 2382 | } 2383 | } 2384 | 2385 | /* CONNECT requests can only contain "hostname:port" */ 2386 | if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { 2387 | return 1; 2388 | } 2389 | 2390 | if (u->field_set & (1 << UF_PORT)) { 2391 | /* Don't bother with endp; we've already validated the string */ 2392 | unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); 2393 | 2394 | /* Ports have a max value of 2^16 */ 2395 | if (v > 0xffff) { 2396 | return 1; 2397 | } 2398 | 2399 | u->port = (uint16_t) v; 2400 | } 2401 | 2402 | return 0; 2403 | } 2404 | 2405 | void 2406 | http_parser_pause(http_parser *parser, int paused) { 2407 | /* Users should only be pausing/unpausing a parser that is not in an error 2408 | * state. In non-debug builds, there's not much that we can do about this 2409 | * other than ignore it. 2410 | */ 2411 | if (HTTP_PARSER_ERRNO(parser) == HPE_OK || 2412 | HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { 2413 | SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); 2414 | } else { 2415 | assert(0 && "Attempting to pause parser in error state"); 2416 | } 2417 | } 2418 | 2419 | int 2420 | http_body_is_final(const struct http_parser *parser) { 2421 | return parser->state == s_message_done; 2422 | } 2423 | 2424 | unsigned long 2425 | http_parser_version(void) { 2426 | return HTTP_PARSER_VERSION_MAJOR * 0x10000 | 2427 | HTTP_PARSER_VERSION_MINOR * 0x00100 | 2428 | HTTP_PARSER_VERSION_PATCH * 0x00001; 2429 | } 2430 | -------------------------------------------------------------------------------- /http-parser/http_parser.gyp: -------------------------------------------------------------------------------- 1 | # This file is used with the GYP meta build system. 2 | # http://code.google.com/p/gyp/ 3 | # To build try this: 4 | # svn co http://gyp.googlecode.com/svn/trunk gyp 5 | # ./gyp/gyp -f make --depth=`pwd` http_parser.gyp 6 | # ./out/Debug/test 7 | { 8 | 'target_defaults': { 9 | 'default_configuration': 'Debug', 10 | 'configurations': { 11 | # TODO: hoist these out and put them somewhere common, because 12 | # RuntimeLibrary MUST MATCH across the entire project 13 | 'Debug': { 14 | 'defines': [ 'DEBUG', '_DEBUG' ], 15 | 'cflags': [ '-Wall', '-Wextra', '-O0', '-g', '-ftrapv' ], 16 | 'msvs_settings': { 17 | 'VCCLCompilerTool': { 18 | 'RuntimeLibrary': 1, # static debug 19 | }, 20 | }, 21 | }, 22 | 'Release': { 23 | 'defines': [ 'NDEBUG' ], 24 | 'cflags': [ '-Wall', '-Wextra', '-O3' ], 25 | 'msvs_settings': { 26 | 'VCCLCompilerTool': { 27 | 'RuntimeLibrary': 0, # static release 28 | }, 29 | }, 30 | } 31 | }, 32 | 'msvs_settings': { 33 | 'VCCLCompilerTool': { 34 | }, 35 | 'VCLibrarianTool': { 36 | }, 37 | 'VCLinkerTool': { 38 | 'GenerateDebugInformation': 'true', 39 | }, 40 | }, 41 | 'conditions': [ 42 | ['OS == "win"', { 43 | 'defines': [ 44 | 'WIN32' 45 | ], 46 | }] 47 | ], 48 | }, 49 | 50 | 'targets': [ 51 | { 52 | 'target_name': 'http_parser', 53 | 'type': 'static_library', 54 | 'include_dirs': [ '.' ], 55 | 'direct_dependent_settings': { 56 | 'defines': [ 'HTTP_PARSER_STRICT=0' ], 57 | 'include_dirs': [ '.' ], 58 | }, 59 | 'defines': [ 'HTTP_PARSER_STRICT=0' ], 60 | 'sources': [ './http_parser.c', ], 61 | 'conditions': [ 62 | ['OS=="win"', { 63 | 'msvs_settings': { 64 | 'VCCLCompilerTool': { 65 | # Compile as C++. http_parser.c is actually C99, but C++ is 66 | # close enough in this case. 67 | 'CompileAs': 2, 68 | }, 69 | }, 70 | }] 71 | ], 72 | }, 73 | 74 | { 75 | 'target_name': 'http_parser_strict', 76 | 'type': 'static_library', 77 | 'include_dirs': [ '.' ], 78 | 'direct_dependent_settings': { 79 | 'defines': [ 'HTTP_PARSER_STRICT=1' ], 80 | 'include_dirs': [ '.' ], 81 | }, 82 | 'defines': [ 'HTTP_PARSER_STRICT=1' ], 83 | 'sources': [ './http_parser.c', ], 84 | 'conditions': [ 85 | ['OS=="win"', { 86 | 'msvs_settings': { 87 | 'VCCLCompilerTool': { 88 | # Compile as C++. http_parser.c is actually C99, but C++ is 89 | # close enough in this case. 90 | 'CompileAs': 2, 91 | }, 92 | }, 93 | }] 94 | ], 95 | }, 96 | 97 | { 98 | 'target_name': 'test-nonstrict', 99 | 'type': 'executable', 100 | 'dependencies': [ 'http_parser' ], 101 | 'sources': [ 'test.c' ] 102 | }, 103 | 104 | { 105 | 'target_name': 'test-strict', 106 | 'type': 'executable', 107 | 'dependencies': [ 'http_parser_strict' ], 108 | 'sources': [ 'test.c' ] 109 | } 110 | ] 111 | } 112 | -------------------------------------------------------------------------------- /http-parser/http_parser.h: -------------------------------------------------------------------------------- 1 | /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifndef http_parser_h 22 | #define http_parser_h 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | /* Also update SONAME in the Makefile whenever you change these. */ 28 | #define HTTP_PARSER_VERSION_MAJOR 2 29 | #define HTTP_PARSER_VERSION_MINOR 5 30 | #define HTTP_PARSER_VERSION_PATCH 0 31 | 32 | #include 33 | #if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) 34 | #include 35 | #include 36 | typedef __int8 int8_t; 37 | typedef unsigned __int8 uint8_t; 38 | typedef __int16 int16_t; 39 | typedef unsigned __int16 uint16_t; 40 | typedef __int32 int32_t; 41 | typedef unsigned __int32 uint32_t; 42 | typedef __int64 int64_t; 43 | typedef unsigned __int64 uint64_t; 44 | #else 45 | #include 46 | #endif 47 | 48 | /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run 49 | * faster 50 | */ 51 | #ifndef HTTP_PARSER_STRICT 52 | # define HTTP_PARSER_STRICT 1 53 | #endif 54 | 55 | /* Maximium header size allowed. If the macro is not defined 56 | * before including this header then the default is used. To 57 | * change the maximum header size, define the macro in the build 58 | * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove 59 | * the effective limit on the size of the header, define the macro 60 | * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) 61 | */ 62 | #ifndef HTTP_MAX_HEADER_SIZE 63 | # define HTTP_MAX_HEADER_SIZE (80*1024) 64 | #endif 65 | 66 | typedef struct http_parser http_parser; 67 | typedef struct http_parser_settings http_parser_settings; 68 | 69 | 70 | /* Callbacks should return non-zero to indicate an error. The parser will 71 | * then halt execution. 72 | * 73 | * The one exception is on_headers_complete. In a HTTP_RESPONSE parser 74 | * returning '1' from on_headers_complete will tell the parser that it 75 | * should not expect a body. This is used when receiving a response to a 76 | * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: 77 | * chunked' headers that indicate the presence of a body. 78 | * 79 | * http_data_cb does not return data chunks. It will be called arbitrarily 80 | * many times for each string. E.G. you might get 10 callbacks for "on_url" 81 | * each providing just a few characters more data. 82 | */ 83 | typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); 84 | typedef int (*http_cb) (http_parser*); 85 | 86 | 87 | /* Request Methods */ 88 | #define HTTP_METHOD_MAP(XX) \ 89 | XX(0, DELETE, DELETE) \ 90 | XX(1, GET, GET) \ 91 | XX(2, HEAD, HEAD) \ 92 | XX(3, POST, POST) \ 93 | XX(4, PUT, PUT) \ 94 | /* pathological */ \ 95 | XX(5, CONNECT, CONNECT) \ 96 | XX(6, OPTIONS, OPTIONS) \ 97 | XX(7, TRACE, TRACE) \ 98 | /* webdav */ \ 99 | XX(8, COPY, COPY) \ 100 | XX(9, LOCK, LOCK) \ 101 | XX(10, MKCOL, MKCOL) \ 102 | XX(11, MOVE, MOVE) \ 103 | XX(12, PROPFIND, PROPFIND) \ 104 | XX(13, PROPPATCH, PROPPATCH) \ 105 | XX(14, SEARCH, SEARCH) \ 106 | XX(15, UNLOCK, UNLOCK) \ 107 | /* subversion */ \ 108 | XX(16, REPORT, REPORT) \ 109 | XX(17, MKACTIVITY, MKACTIVITY) \ 110 | XX(18, CHECKOUT, CHECKOUT) \ 111 | XX(19, MERGE, MERGE) \ 112 | /* upnp */ \ 113 | XX(20, MSEARCH, M-SEARCH) \ 114 | XX(21, NOTIFY, NOTIFY) \ 115 | XX(22, SUBSCRIBE, SUBSCRIBE) \ 116 | XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \ 117 | /* RFC-5789 */ \ 118 | XX(24, PATCH, PATCH) \ 119 | XX(25, PURGE, PURGE) \ 120 | /* CalDAV */ \ 121 | XX(26, MKCALENDAR, MKCALENDAR) \ 122 | 123 | enum http_method 124 | { 125 | #define XX(num, name, string) HTTP_##name = num, 126 | HTTP_METHOD_MAP(XX) 127 | #undef XX 128 | }; 129 | 130 | 131 | enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; 132 | 133 | 134 | /* Flag values for http_parser.flags field */ 135 | enum flags 136 | { F_CHUNKED = 1 << 0 137 | , F_CONNECTION_KEEP_ALIVE = 1 << 1 138 | , F_CONNECTION_CLOSE = 1 << 2 139 | , F_CONNECTION_UPGRADE = 1 << 3 140 | , F_TRAILING = 1 << 4 141 | , F_UPGRADE = 1 << 5 142 | , F_SKIPBODY = 1 << 6 143 | }; 144 | 145 | 146 | /* Map for errno-related constants 147 | * 148 | * The provided argument should be a macro that takes 2 arguments. 149 | */ 150 | #define HTTP_ERRNO_MAP(XX) \ 151 | /* No error */ \ 152 | XX(OK, "success") \ 153 | \ 154 | /* Callback-related errors */ \ 155 | XX(CB_message_begin, "the on_message_begin callback failed") \ 156 | XX(CB_url, "the on_url callback failed") \ 157 | XX(CB_header_field, "the on_header_field callback failed") \ 158 | XX(CB_header_value, "the on_header_value callback failed") \ 159 | XX(CB_headers_complete, "the on_headers_complete callback failed") \ 160 | XX(CB_body, "the on_body callback failed") \ 161 | XX(CB_message_complete, "the on_message_complete callback failed") \ 162 | XX(CB_status, "the on_status callback failed") \ 163 | XX(CB_chunk_header, "the on_chunk_header callback failed") \ 164 | XX(CB_chunk_complete, "the on_chunk_complete callback failed") \ 165 | \ 166 | /* Parsing-related errors */ \ 167 | XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ 168 | XX(HEADER_OVERFLOW, \ 169 | "too many header bytes seen; overflow detected") \ 170 | XX(CLOSED_CONNECTION, \ 171 | "data received after completed connection: close message") \ 172 | XX(INVALID_VERSION, "invalid HTTP version") \ 173 | XX(INVALID_STATUS, "invalid HTTP status code") \ 174 | XX(INVALID_METHOD, "invalid HTTP method") \ 175 | XX(INVALID_URL, "invalid URL") \ 176 | XX(INVALID_HOST, "invalid host") \ 177 | XX(INVALID_PORT, "invalid port") \ 178 | XX(INVALID_PATH, "invalid path") \ 179 | XX(INVALID_QUERY_STRING, "invalid query string") \ 180 | XX(INVALID_FRAGMENT, "invalid fragment") \ 181 | XX(LF_EXPECTED, "LF character expected") \ 182 | XX(INVALID_HEADER_TOKEN, "invalid character in header") \ 183 | XX(INVALID_CONTENT_LENGTH, \ 184 | "invalid character in content-length header") \ 185 | XX(INVALID_CHUNK_SIZE, \ 186 | "invalid character in chunk size header") \ 187 | XX(INVALID_CONSTANT, "invalid constant string") \ 188 | XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ 189 | XX(STRICT, "strict mode assertion failed") \ 190 | XX(PAUSED, "parser is paused") \ 191 | XX(UNKNOWN, "an unknown error occurred") 192 | 193 | 194 | /* Define HPE_* values for each errno value above */ 195 | #define HTTP_ERRNO_GEN(n, s) HPE_##n, 196 | enum http_errno { 197 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 198 | }; 199 | #undef HTTP_ERRNO_GEN 200 | 201 | 202 | /* Get an http_errno value from an http_parser */ 203 | #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) 204 | 205 | 206 | struct http_parser { 207 | /** PRIVATE **/ 208 | unsigned int type : 2; /* enum http_parser_type */ 209 | unsigned int flags : 7; /* F_* values from 'flags' enum; semi-public */ 210 | unsigned int state : 7; /* enum state from http_parser.c */ 211 | unsigned int header_state : 8; /* enum header_state from http_parser.c */ 212 | unsigned int index : 8; /* index into current matcher */ 213 | 214 | uint32_t nread; /* # bytes read in various scenarios */ 215 | uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ 216 | 217 | /** READ-ONLY **/ 218 | unsigned short http_major; 219 | unsigned short http_minor; 220 | unsigned int status_code : 16; /* responses only */ 221 | unsigned int method : 8; /* requests only */ 222 | unsigned int http_errno : 7; 223 | 224 | /* 1 = Upgrade header was present and the parser has exited because of that. 225 | * 0 = No upgrade header present. 226 | * Should be checked when http_parser_execute() returns in addition to 227 | * error checking. 228 | */ 229 | unsigned int upgrade : 1; 230 | 231 | /** PUBLIC **/ 232 | void *data; /* A pointer to get hook to the "connection" or "socket" object */ 233 | }; 234 | 235 | 236 | struct http_parser_settings { 237 | http_cb on_message_begin; 238 | http_data_cb on_url; 239 | http_data_cb on_status; 240 | http_data_cb on_header_field; 241 | http_data_cb on_header_value; 242 | http_cb on_headers_complete; 243 | http_data_cb on_body; 244 | http_cb on_message_complete; 245 | /* When on_chunk_header is called, the current chunk length is stored 246 | * in parser->content_length. 247 | */ 248 | http_cb on_chunk_header; 249 | http_cb on_chunk_complete; 250 | }; 251 | 252 | 253 | enum http_parser_url_fields 254 | { UF_SCHEMA = 0 255 | , UF_HOST = 1 256 | , UF_PORT = 2 257 | , UF_PATH = 3 258 | , UF_QUERY = 4 259 | , UF_FRAGMENT = 5 260 | , UF_USERINFO = 6 261 | , UF_MAX = 7 262 | }; 263 | 264 | 265 | /* Result structure for http_parser_parse_url(). 266 | * 267 | * Callers should index into field_data[] with UF_* values iff field_set 268 | * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and 269 | * because we probably have padding left over), we convert any port to 270 | * a uint16_t. 271 | */ 272 | struct http_parser_url { 273 | uint16_t field_set; /* Bitmask of (1 << UF_*) values */ 274 | uint16_t port; /* Converted UF_PORT string */ 275 | 276 | struct { 277 | uint16_t off; /* Offset into buffer in which field starts */ 278 | uint16_t len; /* Length of run in buffer */ 279 | } field_data[UF_MAX]; 280 | }; 281 | 282 | 283 | /* Returns the library version. Bits 16-23 contain the major version number, 284 | * bits 8-15 the minor version number and bits 0-7 the patch level. 285 | * Usage example: 286 | * 287 | * unsigned long version = http_parser_version(); 288 | * unsigned major = (version >> 16) & 255; 289 | * unsigned minor = (version >> 8) & 255; 290 | * unsigned patch = version & 255; 291 | * printf("http_parser v%u.%u.%u\n", major, minor, patch); 292 | */ 293 | unsigned long http_parser_version(void); 294 | 295 | void http_parser_init(http_parser *parser, enum http_parser_type type); 296 | 297 | 298 | /* Initialize http_parser_settings members to 0 299 | */ 300 | void http_parser_settings_init(http_parser_settings *settings); 301 | 302 | 303 | /* Executes the parser. Returns number of parsed bytes. Sets 304 | * `parser->http_errno` on error. */ 305 | size_t http_parser_execute(http_parser *parser, 306 | const http_parser_settings *settings, 307 | const char *data, 308 | size_t len); 309 | 310 | 311 | /* If http_should_keep_alive() in the on_headers_complete or 312 | * on_message_complete callback returns 0, then this should be 313 | * the last message on the connection. 314 | * If you are the server, respond with the "Connection: close" header. 315 | * If you are the client, close the connection. 316 | */ 317 | int http_should_keep_alive(const http_parser *parser); 318 | 319 | /* Returns a string version of the HTTP method. */ 320 | const char *http_method_str(enum http_method m); 321 | 322 | /* Return a string name of the given error */ 323 | const char *http_errno_name(enum http_errno err); 324 | 325 | /* Return a string description of the given error */ 326 | const char *http_errno_description(enum http_errno err); 327 | 328 | /* Parse a URL; return nonzero on failure */ 329 | int http_parser_parse_url(const char *buf, size_t buflen, 330 | int is_connect, 331 | struct http_parser_url *u); 332 | 333 | /* Pause or un-pause the parser; a nonzero value pauses */ 334 | void http_parser_pause(http_parser *parser, int paused); 335 | 336 | /* Checks if this is the final chunk of the body. */ 337 | int http_body_is_final(const http_parser *parser); 338 | 339 | #ifdef __cplusplus 340 | } 341 | #endif 342 | #endif 343 | -------------------------------------------------------------------------------- /qhttpserver.pri: -------------------------------------------------------------------------------- 1 | isEmpty(PREFIX):PREFIX = /usr/local 2 | isEmpty(LIBDIR):LIBDIR = $${PREFIX}/lib 3 | isEmpty(INCLUDEDIR):INCLUDEDIR = $${PREFIX}/include 4 | -------------------------------------------------------------------------------- /qhttpserver.pro: -------------------------------------------------------------------------------- 1 | CONFIG += ordered 2 | 3 | TEMPLATE = subdirs 4 | 5 | SUBDIRS += src \ 6 | examples 7 | 8 | examples.depends = src 9 | -------------------------------------------------------------------------------- /src/qhttpconnection.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2014 Nikhil Marathe 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "qhttpconnection.h" 24 | 25 | #include 26 | #include 27 | 28 | #include "http_parser.h" 29 | #include "qhttprequest.h" 30 | #include "qhttpresponse.h" 31 | 32 | /// @cond nodoc 33 | 34 | QHttpConnection::QHttpConnection(QTcpSocket *socket, QObject *parent) 35 | : QObject(parent), 36 | m_socket(socket), 37 | m_parser(0), 38 | m_parserSettings(0), 39 | m_request(0), 40 | m_transmitLen(0), 41 | m_transmitPos(0) 42 | { 43 | m_parser = (http_parser *)malloc(sizeof(http_parser)); 44 | http_parser_init(m_parser, HTTP_REQUEST); 45 | 46 | m_parserSettings = new http_parser_settings(); 47 | m_parserSettings->on_message_begin = MessageBegin; 48 | m_parserSettings->on_url = Url; 49 | m_parserSettings->on_header_field = HeaderField; 50 | m_parserSettings->on_header_value = HeaderValue; 51 | m_parserSettings->on_headers_complete = HeadersComplete; 52 | m_parserSettings->on_body = Body; 53 | m_parserSettings->on_message_complete = MessageComplete; 54 | 55 | m_parser->data = this; 56 | 57 | connect(socket, SIGNAL(readyRead()), this, SLOT(parseRequest())); 58 | connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnected())); 59 | connect(socket, SIGNAL(bytesWritten(qint64)), this, SLOT(updateWriteCount(qint64))); 60 | } 61 | 62 | QHttpConnection::~QHttpConnection() 63 | { 64 | m_socket = 0; 65 | 66 | free(m_parser); 67 | m_parser = 0; 68 | 69 | delete m_parserSettings; 70 | m_parserSettings = 0; 71 | } 72 | 73 | void QHttpConnection::socketDisconnected() 74 | { 75 | invalidateRequest(); 76 | m_socket->deleteLater(); 77 | deleteLater(); 78 | } 79 | 80 | void QHttpConnection::invalidateRequest() 81 | { 82 | if (m_request && !m_request->successful()) { 83 | Q_EMIT m_request->end(); 84 | } 85 | 86 | m_request = NULL; 87 | } 88 | 89 | void QHttpConnection::updateWriteCount(qint64 count) 90 | { 91 | Q_ASSERT(m_transmitPos + count <= m_transmitLen); 92 | 93 | m_transmitPos += count; 94 | 95 | if (m_transmitPos == m_transmitLen) 96 | { 97 | m_transmitLen = 0; 98 | m_transmitPos = 0; 99 | Q_EMIT allBytesWritten(); 100 | } 101 | } 102 | 103 | void QHttpConnection::parseRequest() 104 | { 105 | Q_ASSERT(m_parser); 106 | 107 | while (m_socket->bytesAvailable()) { 108 | QByteArray arr = m_socket->readAll(); 109 | http_parser_execute(m_parser, m_parserSettings, arr.constData(), arr.size()); 110 | } 111 | } 112 | 113 | void QHttpConnection::write(const QByteArray &data) 114 | { 115 | m_socket->write(data); 116 | m_transmitLen += data.size(); 117 | } 118 | 119 | void QHttpConnection::flush() 120 | { 121 | m_socket->flush(); 122 | } 123 | 124 | void QHttpConnection::waitForBytesWritten() 125 | { 126 | m_socket->waitForBytesWritten(); 127 | } 128 | 129 | void QHttpConnection::responseDone() 130 | { 131 | QHttpResponse *response = qobject_cast(QObject::sender()); 132 | if (response->m_last) 133 | m_socket->disconnectFromHost(); 134 | } 135 | 136 | /* URL Utilities */ 137 | #define HAS_URL_FIELD(info, field) (info.field_set &(1 << (field))) 138 | 139 | #define GET_FIELD(data, info, field) \ 140 | QString::fromLatin1(data + info.field_data[field].off, info.field_data[field].len) 141 | 142 | #define CHECK_AND_GET_FIELD(data, info, field) \ 143 | (HAS_URL_FIELD(info, field) ? GET_FIELD(data, info, field) : QString()) 144 | 145 | QUrl createUrl(const char *urlData, const http_parser_url &urlInfo) 146 | { 147 | QUrl url; 148 | url.setScheme(CHECK_AND_GET_FIELD(urlData, urlInfo, UF_SCHEMA)); 149 | url.setHost(CHECK_AND_GET_FIELD(urlData, urlInfo, UF_HOST)); 150 | // Port is dealt with separately since it is available as an integer. 151 | url.setPath(CHECK_AND_GET_FIELD(urlData, urlInfo, UF_PATH), QUrl::TolerantMode); 152 | #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) 153 | url.setQuery(CHECK_AND_GET_FIELD(urlData, urlInfo, UF_QUERY)); 154 | #else 155 | if (HAS_URL_FIELD(urlInfo, UF_QUERY)) { 156 | url.setEncodedQuery(QByteArray(urlData + urlInfo.field_data[UF_QUERY].off, 157 | urlInfo.field_data[UF_QUERY].len)); 158 | } 159 | #endif 160 | url.setFragment(CHECK_AND_GET_FIELD(urlData, urlInfo, UF_FRAGMENT)); 161 | url.setUserInfo(CHECK_AND_GET_FIELD(urlData, urlInfo, UF_USERINFO)); 162 | 163 | if (HAS_URL_FIELD(urlInfo, UF_PORT)) 164 | url.setPort(urlInfo.port); 165 | 166 | return url; 167 | } 168 | 169 | #undef CHECK_AND_SET_FIELD 170 | #undef GET_FIELD 171 | #undef HAS_URL_FIELD 172 | 173 | /******************** 174 | * Static Callbacks * 175 | *******************/ 176 | 177 | int QHttpConnection::MessageBegin(http_parser *parser) 178 | { 179 | QHttpConnection *theConnection = static_cast(parser->data); 180 | theConnection->m_currentHeaders.clear(); 181 | theConnection->m_currentUrl.clear(); 182 | theConnection->m_currentUrl.reserve(128); 183 | 184 | // The QHttpRequest should not be parented to this, since it's memory 185 | // management is the responsibility of the user of the library. 186 | theConnection->m_request = new QHttpRequest(theConnection); 187 | 188 | // Invalidate the request when it is deleted to prevent keep-alive requests 189 | // from calling a signal on a deleted object. 190 | connect(theConnection->m_request, SIGNAL(destroyed(QObject*)), theConnection, SLOT(invalidateRequest())); 191 | 192 | return 0; 193 | } 194 | 195 | int QHttpConnection::HeadersComplete(http_parser *parser) 196 | { 197 | QHttpConnection *theConnection = static_cast(parser->data); 198 | Q_ASSERT(theConnection->m_request); 199 | 200 | /** set method **/ 201 | theConnection->m_request->setMethod(static_cast(parser->method)); 202 | 203 | /** set version **/ 204 | theConnection->m_request->setVersion( 205 | QString("%1.%2").arg(parser->http_major).arg(parser->http_minor)); 206 | 207 | /** get parsed url **/ 208 | struct http_parser_url urlInfo; 209 | int r = http_parser_parse_url(theConnection->m_currentUrl.constData(), 210 | theConnection->m_currentUrl.size(), 211 | parser->method == HTTP_CONNECT, &urlInfo); 212 | Q_ASSERT(r == 0); 213 | Q_UNUSED(r); 214 | 215 | theConnection->m_request->setUrl(createUrl(theConnection->m_currentUrl.constData(), urlInfo)); 216 | 217 | // Insert last remaining header 218 | theConnection->m_currentHeaders[theConnection->m_currentHeaderField.toLower()] = 219 | theConnection->m_currentHeaderValue; 220 | theConnection->m_request->setHeaders(theConnection->m_currentHeaders); 221 | 222 | /** set client information **/ 223 | theConnection->m_request->m_remoteAddress = theConnection->m_socket->peerAddress().toString(); 224 | theConnection->m_request->m_remotePort = theConnection->m_socket->peerPort(); 225 | 226 | QHttpResponse *response = new QHttpResponse(theConnection); 227 | if (parser->http_major < 1 || parser->http_minor < 1) 228 | response->m_keepAlive = false; 229 | 230 | connect(theConnection, SIGNAL(destroyed()), response, SLOT(connectionClosed())); 231 | connect(response, SIGNAL(done()), theConnection, SLOT(responseDone())); 232 | 233 | // we are good to go! 234 | Q_EMIT theConnection->newRequest(theConnection->m_request, response); 235 | return 0; 236 | } 237 | 238 | int QHttpConnection::MessageComplete(http_parser *parser) 239 | { 240 | // TODO: do cleanup and prepare for next request 241 | QHttpConnection *theConnection = static_cast(parser->data); 242 | Q_ASSERT(theConnection->m_request); 243 | 244 | theConnection->m_request->setSuccessful(true); 245 | Q_EMIT theConnection->m_request->end(); 246 | return 0; 247 | } 248 | 249 | int QHttpConnection::Url(http_parser *parser, const char *at, size_t length) 250 | { 251 | QHttpConnection *theConnection = static_cast(parser->data); 252 | Q_ASSERT(theConnection->m_request); 253 | 254 | theConnection->m_currentUrl.append(at, length); 255 | return 0; 256 | } 257 | 258 | int QHttpConnection::HeaderField(http_parser *parser, const char *at, size_t length) 259 | { 260 | QHttpConnection *theConnection = static_cast(parser->data); 261 | Q_ASSERT(theConnection->m_request); 262 | 263 | // insert the header we parsed previously 264 | // into the header map 265 | if (!theConnection->m_currentHeaderField.isEmpty() && 266 | !theConnection->m_currentHeaderValue.isEmpty()) { 267 | // header names are always lower-cased 268 | theConnection->m_currentHeaders[theConnection->m_currentHeaderField.toLower()] = 269 | theConnection->m_currentHeaderValue; 270 | // clear header value. this sets up a nice 271 | // feedback loop where the next time 272 | // HeaderValue is called, it can simply append 273 | theConnection->m_currentHeaderField = QString(); 274 | theConnection->m_currentHeaderValue = QString(); 275 | } 276 | 277 | QString fieldSuffix = QString::fromLatin1(at, length); 278 | theConnection->m_currentHeaderField += fieldSuffix; 279 | return 0; 280 | } 281 | 282 | int QHttpConnection::HeaderValue(http_parser *parser, const char *at, size_t length) 283 | { 284 | QHttpConnection *theConnection = static_cast(parser->data); 285 | Q_ASSERT(theConnection->m_request); 286 | 287 | QString valueSuffix = QString::fromLatin1(at, length); 288 | theConnection->m_currentHeaderValue += valueSuffix; 289 | return 0; 290 | } 291 | 292 | int QHttpConnection::Body(http_parser *parser, const char *at, size_t length) 293 | { 294 | QHttpConnection *theConnection = static_cast(parser->data); 295 | Q_ASSERT(theConnection->m_request); 296 | 297 | Q_EMIT theConnection->m_request->data(QByteArray(at, length)); 298 | return 0; 299 | } 300 | 301 | /// @endcond 302 | -------------------------------------------------------------------------------- /src/qhttpconnection.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2014 Nikhil Marathe 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #ifndef Q_HTTP_CONNECTION 24 | #define Q_HTTP_CONNECTION 25 | 26 | #include "qhttpserverapi.h" 27 | #include "qhttpserverfwd.h" 28 | 29 | #include 30 | 31 | /// @cond nodoc 32 | 33 | class QHTTPSERVER_API QHttpConnection : public QObject 34 | { 35 | Q_OBJECT 36 | 37 | public: 38 | QHttpConnection(QTcpSocket *socket, QObject *parent = 0); 39 | virtual ~QHttpConnection(); 40 | 41 | void write(const QByteArray &data); 42 | void flush(); 43 | void waitForBytesWritten(); 44 | 45 | Q_SIGNALS: 46 | void newRequest(QHttpRequest *, QHttpResponse *); 47 | void allBytesWritten(); 48 | 49 | private Q_SLOTS: 50 | void parseRequest(); 51 | void responseDone(); 52 | void socketDisconnected(); 53 | void invalidateRequest(); 54 | void updateWriteCount(qint64); 55 | 56 | private: 57 | static int MessageBegin(http_parser *parser); 58 | static int Url(http_parser *parser, const char *at, size_t length); 59 | static int HeaderField(http_parser *parser, const char *at, size_t length); 60 | static int HeaderValue(http_parser *parser, const char *at, size_t length); 61 | static int HeadersComplete(http_parser *parser); 62 | static int Body(http_parser *parser, const char *at, size_t length); 63 | static int MessageComplete(http_parser *parser); 64 | 65 | private: 66 | QTcpSocket *m_socket; 67 | http_parser *m_parser; 68 | http_parser_settings *m_parserSettings; 69 | 70 | // Since there can only be one request at any time even with pipelining. 71 | QHttpRequest *m_request; 72 | 73 | QByteArray m_currentUrl; 74 | // The ones we are reading in from the parser 75 | HeaderHash m_currentHeaders; 76 | QString m_currentHeaderField; 77 | QString m_currentHeaderValue; 78 | 79 | // Keep track of transmit buffer status 80 | qint64 m_transmitLen; 81 | qint64 m_transmitPos; 82 | }; 83 | 84 | /// @endcond 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /src/qhttprequest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2014 Nikhil Marathe 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "qhttprequest.h" 24 | 25 | #include "qhttpconnection.h" 26 | 27 | QHttpRequest::QHttpRequest(QHttpConnection *connection, QObject *parent) 28 | : QObject(parent), m_connection(connection), m_url("http://localhost/"), m_success(false) 29 | { 30 | } 31 | 32 | QHttpRequest::~QHttpRequest() 33 | { 34 | } 35 | 36 | QString QHttpRequest::header(const QString &field) 37 | { 38 | return m_headers.value(field.toLower(), ""); 39 | } 40 | 41 | const HeaderHash &QHttpRequest::headers() const 42 | { 43 | return m_headers; 44 | } 45 | 46 | const QString &QHttpRequest::httpVersion() const 47 | { 48 | return m_version; 49 | } 50 | 51 | const QUrl &QHttpRequest::url() const 52 | { 53 | return m_url; 54 | } 55 | 56 | const QString QHttpRequest::path() const 57 | { 58 | return m_url.path(); 59 | } 60 | 61 | const QString QHttpRequest::methodString() const 62 | { 63 | return MethodToString(method()); 64 | } 65 | 66 | QHttpRequest::HttpMethod QHttpRequest::method() const 67 | { 68 | return m_method; 69 | } 70 | 71 | const QString &QHttpRequest::remoteAddress() const 72 | { 73 | return m_remoteAddress; 74 | } 75 | 76 | quint16 QHttpRequest::remotePort() const 77 | { 78 | return m_remotePort; 79 | } 80 | 81 | void QHttpRequest::storeBody() 82 | { 83 | connect(this, SIGNAL(data(const QByteArray &)), this, SLOT(appendBody(const QByteArray &)), 84 | Qt::UniqueConnection); 85 | } 86 | 87 | QString QHttpRequest::MethodToString(HttpMethod method) 88 | { 89 | int index = staticMetaObject.indexOfEnumerator("HttpMethod"); 90 | return staticMetaObject.enumerator(index).valueToKey(method); 91 | } 92 | 93 | void QHttpRequest::appendBody(const QByteArray &body) 94 | { 95 | m_body.append(body); 96 | } 97 | -------------------------------------------------------------------------------- /src/qhttprequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2014 Nikhil Marathe 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #ifndef Q_HTTP_REQUEST 24 | #define Q_HTTP_REQUEST 25 | 26 | #include "qhttpserverapi.h" 27 | #include "qhttpserverfwd.h" 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | /// The QHttpRequest class represents the header and body data sent by the client. 35 | /** The requests header data is available immediately. Body data is streamed as 36 | it comes in via the data() signal. As a consequence the application's request 37 | callback should ensure that it connects to the data() signal before control 38 | returns back to the event loop. Otherwise there is a risk of some data never 39 | being received by the application. 40 | 41 | The class is read-only. */ 42 | class QHTTPSERVER_API QHttpRequest : public QObject 43 | { 44 | Q_OBJECT 45 | 46 | Q_PROPERTY(HeaderHash headers READ headers) 47 | Q_PROPERTY(QString remoteAddress READ remoteAddress) 48 | Q_PROPERTY(quint16 remotePort READ remotePort) 49 | Q_PROPERTY(QString method READ method) 50 | Q_PROPERTY(QUrl url READ url) 51 | Q_PROPERTY(QString path READ path) 52 | Q_PROPERTY(QString httpVersion READ httpVersion) 53 | 54 | Q_ENUMS(HttpMethod) 55 | 56 | /// @cond nodoc 57 | friend class QHttpConnection; 58 | /// @endcond 59 | 60 | public: 61 | virtual ~QHttpRequest(); 62 | 63 | /// Request method enumeration. 64 | /** @note Taken from http_parser.h -- make sure to keep synced */ 65 | enum HttpMethod { 66 | HTTP_DELETE = 0, 67 | HTTP_GET, 68 | HTTP_HEAD, 69 | HTTP_POST, 70 | HTTP_PUT, 71 | // pathological 72 | HTTP_CONNECT, 73 | HTTP_OPTIONS, 74 | HTTP_TRACE, 75 | // webdav 76 | HTTP_COPY, 77 | HTTP_LOCK, 78 | HTTP_MKCOL, 79 | HTTP_MOVE, 80 | HTTP_PROPFIND, 81 | HTTP_PROPPATCH, 82 | HTTP_SEARCH, 83 | HTTP_UNLOCK, 84 | // subversion 85 | HTTP_REPORT, 86 | HTTP_MKACTIVITY, 87 | HTTP_CHECKOUT, 88 | HTTP_MERGE, 89 | // upnp 90 | HTTP_MSEARCH, 91 | HTTP_NOTIFY, 92 | HTTP_SUBSCRIBE, 93 | HTTP_UNSUBSCRIBE, 94 | // RFC-5789 95 | HTTP_PATCH, 96 | HTTP_PURGE 97 | }; 98 | 99 | /// The method used for the request. 100 | HttpMethod method() const; 101 | 102 | /// Returns the method string for the request. 103 | /** @note This will plainly transform the enum into a string HTTP_GET -> "HTTP_GET". */ 104 | const QString methodString() const; 105 | 106 | /// The complete URL for the request. 107 | /** This includes the path and query string. 108 | @sa path() */ 109 | const QUrl &url() const; 110 | 111 | /// The path portion of the query URL. 112 | /** @sa url() */ 113 | const QString path() const; 114 | 115 | /// The HTTP version of the request. 116 | /** @return A string in the form of "x.x" */ 117 | const QString &httpVersion() const; 118 | 119 | /// Return all the headers sent by the client. 120 | /** This returns a reference. If you want to store headers 121 | somewhere else, where the request may be deleted, 122 | make sure you store them as a copy. 123 | @note All header names are lowercase 124 | so that Content-Length becomes content-length etc. */ 125 | const HeaderHash &headers() const; 126 | 127 | /// Get the value of a header. 128 | /** Headers are stored as lowercase so the input @c field will be lowercased. 129 | @param field Name of the header field 130 | @return Value of the header or empty string if not found. */ 131 | QString header(const QString &field); 132 | 133 | /// IP Address of the client in dotted decimal format. 134 | const QString &remoteAddress() const; 135 | 136 | /// Outbound connection port for the client. 137 | quint16 remotePort() const; 138 | 139 | /// Request body data, empty for non POST/PUT requests. 140 | /** @sa storeBody() */ 141 | const QByteArray &body() const 142 | { 143 | return m_body; 144 | } 145 | 146 | /// If this request was successfully received. 147 | /** Set before end() has been emitted, stating whether 148 | the message was properly received. This is false 149 | until the receiving the full request has completed. */ 150 | bool successful() const 151 | { 152 | return m_success; 153 | } 154 | 155 | /// Utility function to make this request store all body data internally. 156 | /** If you call this when the request is received via QHttpServer::newRequest() 157 | the request will take care of storing the body data for you. 158 | Once the end() signal is emitted you can access the body data with 159 | the body() function. 160 | 161 | If you wish to handle incoming data yourself don't call this function 162 | and see the data() signal. 163 | @sa data() body() */ 164 | void storeBody(); 165 | 166 | Q_SIGNALS: 167 | /// Emitted when new body data has been received. 168 | /** @note This may be emitted zero or more times 169 | depending on the request type. 170 | @param data Received data. */ 171 | void data(const QByteArray &data); 172 | 173 | /// Emitted when the request has been fully received. 174 | /** @note The no more data() signals will be emitted after this. */ 175 | void end(); 176 | 177 | private Q_SLOTS: 178 | void appendBody(const QByteArray &body); 179 | 180 | private: 181 | QHttpRequest(QHttpConnection *connection, QObject *parent = 0); 182 | 183 | static QString MethodToString(HttpMethod method); 184 | 185 | void setMethod(HttpMethod method) { m_method = method; } 186 | void setVersion(const QString &version) { m_version = version; } 187 | void setUrl(const QUrl &url) { m_url = url; } 188 | void setHeaders(const HeaderHash headers) { m_headers = headers; } 189 | void setSuccessful(bool success) { m_success = success; } 190 | 191 | QHttpConnection *m_connection; 192 | HeaderHash m_headers; 193 | HttpMethod m_method; 194 | QUrl m_url; 195 | QString m_version; 196 | QString m_remoteAddress; 197 | quint16 m_remotePort; 198 | QByteArray m_body; 199 | bool m_success; 200 | }; 201 | 202 | #endif 203 | -------------------------------------------------------------------------------- /src/qhttpresponse.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2014 Nikhil Marathe 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "qhttpresponse.h" 24 | 25 | #include 26 | #include 27 | 28 | #include "qhttpserver.h" 29 | #include "qhttpconnection.h" 30 | 31 | QHttpResponse::QHttpResponse(QHttpConnection *connection) 32 | // TODO: parent child relation 33 | : QObject(0), 34 | m_connection(connection), 35 | m_headerWritten(false), 36 | m_sentConnectionHeader(false), 37 | m_sentContentLengthHeader(false), 38 | m_sentTransferEncodingHeader(false), 39 | m_sentDate(false), 40 | m_keepAlive(true), 41 | m_last(false), 42 | m_useChunkedEncoding(false), 43 | m_finished(false) 44 | { 45 | connect(m_connection, SIGNAL(allBytesWritten()), this, SIGNAL(allBytesWritten())); 46 | } 47 | 48 | QHttpResponse::~QHttpResponse() 49 | { 50 | } 51 | 52 | void QHttpResponse::setHeader(const QString &field, const QString &value) 53 | { 54 | if (!m_finished) 55 | m_headers[field] = value; 56 | else 57 | qWarning() << "QHttpResponse::setHeader() Cannot set headers after response has finished."; 58 | } 59 | 60 | void QHttpResponse::writeHeader(const char *field, const QString &value) 61 | { 62 | if (!m_finished) { 63 | m_connection->write(field); 64 | m_connection->write(": "); 65 | m_connection->write(value.toUtf8()); 66 | m_connection->write("\r\n"); 67 | } else 68 | qWarning() 69 | << "QHttpResponse::writeHeader() Cannot write headers after response has finished."; 70 | } 71 | 72 | void QHttpResponse::writeHeaders() 73 | { 74 | if (m_finished) 75 | return; 76 | 77 | foreach(const QString & name, m_headers.keys()) { 78 | QString value = m_headers[name]; 79 | if (name.compare("connection", Qt::CaseInsensitive) == 0) { 80 | m_sentConnectionHeader = true; 81 | if (value.compare("close", Qt::CaseInsensitive) == 0) 82 | m_last = true; 83 | else 84 | m_keepAlive = true; 85 | } else if (name.compare("transfer-encoding", Qt::CaseInsensitive) == 0) { 86 | m_sentTransferEncodingHeader = true; 87 | if (value.compare("chunked", Qt::CaseInsensitive) == 0) 88 | m_useChunkedEncoding = true; 89 | } else if (name.compare("content-length", Qt::CaseInsensitive) == 0) 90 | m_sentContentLengthHeader = true; 91 | else if (name.compare("date", Qt::CaseInsensitive) == 0) 92 | m_sentDate = true; 93 | 94 | /// @todo Expect case (??) 95 | 96 | writeHeader(name.toLatin1(), value.toLatin1()); 97 | } 98 | 99 | if (!m_sentConnectionHeader) { 100 | if (m_keepAlive && (m_sentContentLengthHeader || m_useChunkedEncoding)) { 101 | writeHeader("Connection", "keep-alive"); 102 | } else { 103 | m_last = true; 104 | writeHeader("Connection", "close"); 105 | } 106 | } 107 | 108 | if (!m_sentContentLengthHeader && !m_sentTransferEncodingHeader) { 109 | if (m_useChunkedEncoding) 110 | writeHeader("Transfer-Encoding", "chunked"); 111 | else 112 | m_last = true; 113 | } 114 | 115 | // Sun, 06 Nov 1994 08:49:37 GMT - RFC 822. Use QLocale::c() so english is used for month and 116 | // day. 117 | if (!m_sentDate) 118 | writeHeader("Date", 119 | QLocale::c().toString(QDateTime::currentDateTimeUtc(), 120 | "ddd, dd MMM yyyy hh:mm:ss") + " GMT"); 121 | } 122 | 123 | void QHttpResponse::writeHead(int status) 124 | { 125 | if (m_finished) { 126 | qWarning() 127 | << "QHttpResponse::writeHead() Cannot write headers after response has finished."; 128 | return; 129 | } 130 | 131 | if (m_headerWritten) { 132 | qWarning() << "QHttpResponse::writeHead() Already called once for this response."; 133 | return; 134 | } 135 | 136 | m_connection->write( 137 | QString("HTTP/1.1 %1 %2\r\n").arg(status).arg(STATUS_CODES[status]).toLatin1()); 138 | writeHeaders(); 139 | m_connection->write("\r\n"); 140 | 141 | m_headerWritten = true; 142 | } 143 | 144 | void QHttpResponse::writeHead(StatusCode statusCode) 145 | { 146 | writeHead(static_cast(statusCode)); 147 | } 148 | 149 | void QHttpResponse::write(const QByteArray &data) 150 | { 151 | if (m_finished) { 152 | qWarning() << "QHttpResponse::write() Cannot write body after response has finished."; 153 | return; 154 | } 155 | 156 | if (!m_headerWritten) { 157 | qWarning() << "QHttpResponse::write() You must call writeHead() before writing body data."; 158 | return; 159 | } 160 | 161 | m_connection->write(data); 162 | } 163 | 164 | void QHttpResponse::flush() 165 | { 166 | m_connection->flush(); 167 | } 168 | 169 | void QHttpResponse::waitForBytesWritten() 170 | { 171 | m_connection->waitForBytesWritten(); 172 | } 173 | 174 | void QHttpResponse::end(const QByteArray &data) 175 | { 176 | if (m_finished) { 177 | qWarning() << "QHttpResponse::end() Cannot write end after response has finished."; 178 | return; 179 | } 180 | 181 | if (data.size() > 0) 182 | write(data); 183 | m_finished = true; 184 | 185 | Q_EMIT done(); 186 | 187 | /// @todo End connection and delete ourselves. Is this a still valid note? 188 | deleteLater(); 189 | } 190 | 191 | void QHttpResponse::connectionClosed() 192 | { 193 | m_finished = true; 194 | Q_EMIT done(); 195 | deleteLater(); 196 | } 197 | -------------------------------------------------------------------------------- /src/qhttpresponse.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2014 Nikhil Marathe 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #ifndef Q_HTTP_RESPONSE 24 | #define Q_HTTP_RESPONSE 25 | 26 | #include "qhttpserverapi.h" 27 | #include "qhttpserverfwd.h" 28 | 29 | #include 30 | 31 | /// The QHttpResponse class handles sending data back to the client as a response to a request. 32 | /** The steps to respond correctly are 33 |
    34 |
  1. Call setHeader() to set headers [optional]
  2. 35 |
  3. Call writeHead() with the HTTP status code
  4. 36 |
  5. Call write() zero or more times for body data.
  6. 37 |
  7. Call end() when the resonse can be sent back
  8. 38 |
*/ 39 | class QHTTPSERVER_API QHttpResponse : public QObject 40 | { 41 | Q_OBJECT 42 | 43 | public: 44 | /// HTTP status code. 45 | enum StatusCode { 46 | STATUS_CONTINUE = 100, 47 | STATUS_SWITCH_PROTOCOLS = 101, 48 | STATUS_OK = 200, 49 | STATUS_CREATED = 201, 50 | STATUS_ACCEPTED = 202, 51 | STATUS_NON_AUTHORITATIVE_INFORMATION = 203, 52 | STATUS_NO_CONTENT = 204, 53 | STATUS_RESET_CONTENT = 205, 54 | STATUS_PARTIAL_CONTENT = 206, 55 | STATUS_MULTIPLE_CHOICES = 300, 56 | STATUS_MOVED_PERMANENTLY = 301, 57 | STATUS_FOUND = 302, 58 | STATUS_SEE_OTHER = 303, 59 | STATUS_NOT_MODIFIED = 304, 60 | STATUS_USE_PROXY = 305, 61 | STATUS_TEMPORARY_REDIRECT = 307, 62 | STATUS_BAD_REQUEST = 400, 63 | STATUS_UNAUTHORIZED = 401, 64 | STATUS_PAYMENT_REQUIRED = 402, 65 | STATUS_FORBIDDEN = 403, 66 | STATUS_NOT_FOUND = 404, 67 | STATUS_METHOD_NOT_ALLOWED = 405, 68 | STATUS_NOT_ACCEPTABLE = 406, 69 | STATUS_PROXY_AUTHENTICATION_REQUIRED = 407, 70 | STATUS_REQUEST_TIMEOUT = 408, 71 | STATUS_CONFLICT = 409, 72 | STATUS_GONE = 410, 73 | STATUS_LENGTH_REQUIRED = 411, 74 | STATUS_PRECONDITION_FAILED = 412, 75 | STATUS_REQUEST_ENTITY_TOO_LARGE = 413, 76 | STATUS_REQUEST_URI_TOO_LONG = 414, 77 | STATUS_REQUEST_UNSUPPORTED_MEDIA_TYPE = 415, 78 | STATUS_REQUESTED_RANGE_NOT_SATISFIABLE = 416, 79 | STATUS_EXPECTATION_FAILED = 417, 80 | STATUS_INTERNAL_SERVER_ERROR = 500, 81 | STATUS_NOT_IMPLEMENTED = 501, 82 | STATUS_BAD_GATEWAY = 502, 83 | STATUS_SERVICE_UNAVAILABLE = 503, 84 | STATUS_GATEWAY_TIMEOUT = 504, 85 | STATUS_HTTP_VERSION_NOT_SUPPORTED = 505 86 | }; 87 | 88 | virtual ~QHttpResponse(); 89 | 90 | /// @cond nodoc 91 | friend class QHttpConnection; 92 | /// @endcond 93 | 94 | public Q_SLOTS: 95 | /// Sets a response header @c field to @c value. 96 | /** @note You must call this with all your custom headers 97 | before calling writeHead(), write() or end(). 98 | @param field Header field to be set. 99 | @param value Header value to be set. */ 100 | void setHeader(const QString &field, const QString &value); 101 | 102 | /// Writes the header section of the response 103 | /// using @c status as the response status code. 104 | /** @param statusCode Status code for the response. 105 | @note Any headers should be set before 106 | invoking this function with setHeader(). */ 107 | void writeHead(int statusCode); 108 | 109 | /** @overload */ 110 | void writeHead(StatusCode statusCode); 111 | 112 | /// Writes a block of @c data to the client. 113 | /** @note writeHead() must be called before this function. */ 114 | void write(const QByteArray &data); 115 | 116 | /// Flushes the written data to the client. 117 | /** @note writeHead() must be called before this function. */ 118 | void flush(); 119 | 120 | /// Waiting for bytes to be written. See QAbstractSocket::waitForBytesWritten in the Qt documentation 121 | /** @note writeHead() must be called before this function. */ 122 | void waitForBytesWritten(); 123 | 124 | /// End/finish the response. 125 | /** Data will be flushed to the underlying socket 126 | and the connection itself will be closed if 127 | this is the last response. 128 | 129 | This will emit done() and queue this object 130 | for deletion. For details see \ref memorymanagement. 131 | @param data Optional data to be written before finishing. */ 132 | void end(const QByteArray &data = ""); 133 | 134 | Q_SIGNALS: 135 | /// Emitted when all the data has been sent 136 | /** This signal indicates that the underlaying socket has transmitted all 137 | of it's buffered data. It is possible to implement memory-efficient 138 | file transfers by calling \ref write() for a block of data only after 139 | receiving this signal. */ 140 | void allBytesWritten(); 141 | 142 | /// Emitted when the response is finished. 143 | /** You should not interact with this object 144 | after done() has been emitted as the object 145 | has already been scheduled for deletion. */ 146 | void done(); 147 | 148 | private: 149 | QHttpResponse(QHttpConnection *connection); 150 | 151 | void writeHeaders(); 152 | void writeHeader(const char *field, const QString &value); 153 | 154 | QHttpConnection *m_connection; 155 | 156 | HeaderHash m_headers; 157 | 158 | bool m_headerWritten; 159 | bool m_sentConnectionHeader; 160 | bool m_sentContentLengthHeader; 161 | bool m_sentTransferEncodingHeader; 162 | bool m_sentDate; 163 | bool m_keepAlive; 164 | bool m_last; 165 | bool m_useChunkedEncoding; 166 | bool m_finished; 167 | 168 | private Q_SLOTS: 169 | void connectionClosed(); 170 | }; 171 | 172 | #endif 173 | -------------------------------------------------------------------------------- /src/qhttpserver.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2014 Nikhil Marathe 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #include "qhttpserver.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "qhttpconnection.h" 31 | 32 | QHash STATUS_CODES; 33 | 34 | QHttpServer::QHttpServer(QObject *parent) : QObject(parent), m_tcpServer(0) 35 | { 36 | #define STATUS_CODE(num, reason) STATUS_CODES.insert(num, reason); 37 | // {{{ 38 | STATUS_CODE(100, "Continue") 39 | STATUS_CODE(101, "Switching Protocols") 40 | STATUS_CODE(102, "Processing") // RFC 2518) obsoleted by RFC 4918 41 | STATUS_CODE(200, "OK") 42 | STATUS_CODE(201, "Created") 43 | STATUS_CODE(202, "Accepted") 44 | STATUS_CODE(203, "Non-Authoritative Information") 45 | STATUS_CODE(204, "No Content") 46 | STATUS_CODE(205, "Reset Content") 47 | STATUS_CODE(206, "Partial Content") 48 | STATUS_CODE(207, "Multi-Status") // RFC 4918 49 | STATUS_CODE(300, "Multiple Choices") 50 | STATUS_CODE(301, "Moved Permanently") 51 | STATUS_CODE(302, "Moved Temporarily") 52 | STATUS_CODE(303, "See Other") 53 | STATUS_CODE(304, "Not Modified") 54 | STATUS_CODE(305, "Use Proxy") 55 | STATUS_CODE(307, "Temporary Redirect") 56 | STATUS_CODE(400, "Bad Request") 57 | STATUS_CODE(401, "Unauthorized") 58 | STATUS_CODE(402, "Payment Required") 59 | STATUS_CODE(403, "Forbidden") 60 | STATUS_CODE(404, "Not Found") 61 | STATUS_CODE(405, "Method Not Allowed") 62 | STATUS_CODE(406, "Not Acceptable") 63 | STATUS_CODE(407, "Proxy Authentication Required") 64 | STATUS_CODE(408, "Request Time-out") 65 | STATUS_CODE(409, "Conflict") 66 | STATUS_CODE(410, "Gone") 67 | STATUS_CODE(411, "Length Required") 68 | STATUS_CODE(412, "Precondition Failed") 69 | STATUS_CODE(413, "Request Entity Too Large") 70 | STATUS_CODE(414, "Request-URI Too Large") 71 | STATUS_CODE(415, "Unsupported Media Type") 72 | STATUS_CODE(416, "Requested Range Not Satisfiable") 73 | STATUS_CODE(417, "Expectation Failed") 74 | STATUS_CODE(418, "I\"m a teapot") // RFC 2324 75 | STATUS_CODE(422, "Unprocessable Entity") // RFC 4918 76 | STATUS_CODE(423, "Locked") // RFC 4918 77 | STATUS_CODE(424, "Failed Dependency") // RFC 4918 78 | STATUS_CODE(425, "Unordered Collection") // RFC 4918 79 | STATUS_CODE(426, "Upgrade Required") // RFC 2817 80 | STATUS_CODE(500, "Internal Server Error") 81 | STATUS_CODE(501, "Not Implemented") 82 | STATUS_CODE(502, "Bad Gateway") 83 | STATUS_CODE(503, "Service Unavailable") 84 | STATUS_CODE(504, "Gateway Time-out") 85 | STATUS_CODE(505, "HTTP Version not supported") 86 | STATUS_CODE(506, "Variant Also Negotiates") // RFC 2295 87 | STATUS_CODE(507, "Insufficient Storage") // RFC 4918 88 | STATUS_CODE(509, "Bandwidth Limit Exceeded") 89 | STATUS_CODE(510, "Not Extended") // RFC 2774 90 | // }}} 91 | } 92 | 93 | QHttpServer::~QHttpServer() 94 | { 95 | } 96 | 97 | void QHttpServer::newConnection() 98 | { 99 | Q_ASSERT(m_tcpServer); 100 | 101 | while (m_tcpServer->hasPendingConnections()) { 102 | QHttpConnection *connection = 103 | new QHttpConnection(m_tcpServer->nextPendingConnection(), this); 104 | connect(connection, SIGNAL(newRequest(QHttpRequest *, QHttpResponse *)), this, 105 | SIGNAL(newRequest(QHttpRequest *, QHttpResponse *))); 106 | } 107 | } 108 | 109 | bool QHttpServer::listen(const QHostAddress &address, quint16 port) 110 | { 111 | Q_ASSERT(!m_tcpServer); 112 | m_tcpServer = new QTcpServer(this); 113 | 114 | bool couldBindToPort = m_tcpServer->listen(address, port); 115 | if (couldBindToPort) { 116 | connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(newConnection())); 117 | } else { 118 | delete m_tcpServer; 119 | m_tcpServer = NULL; 120 | } 121 | return couldBindToPort; 122 | } 123 | 124 | bool QHttpServer::listen(quint16 port) 125 | { 126 | return listen(QHostAddress::Any, port); 127 | } 128 | 129 | void QHttpServer::close() 130 | { 131 | if (m_tcpServer) 132 | m_tcpServer->close(); 133 | } 134 | -------------------------------------------------------------------------------- /src/qhttpserver.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2014 Nikhil Marathe 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #ifndef Q_HTTP_SERVER 24 | #define Q_HTTP_SERVER 25 | 26 | #define QHTTPSERVER_VERSION_MAJOR 0 27 | #define QHTTPSERVER_VERSION_MINOR 1 28 | #define QHTTPSERVER_VERSION_PATCH 0 29 | 30 | #include "qhttpserverapi.h" 31 | #include "qhttpserverfwd.h" 32 | 33 | #include 34 | #include 35 | 36 | /// Maps status codes to string reason phrases 37 | extern QHash STATUS_CODES; 38 | 39 | /// The QHttpServer class forms the basis of the %QHttpServer 40 | /// project. It is a fast, non-blocking HTTP server. 41 | /** These are the steps to create a server, handle and respond to requests: 42 |
    43 |
  1. Create an instance of QHttpServer.
  2. 44 |
  3. Connect a slot to the newRequest() signal.
  4. 45 |
  5. Create a QCoreApplication to drive the server event loop.
  6. 46 |
  7. Respond to clients by writing out to the QHttpResponse object.
  8. 47 |
48 | 49 | Here is a simple sample application on how to use this library 50 | 51 | helloworld.cpp 52 | @include helloworld/helloworld.cpp 53 | 54 | helloworld.h 55 | @include helloworld/helloworld.h */ 56 | class QHTTPSERVER_API QHttpServer : public QObject 57 | { 58 | Q_OBJECT 59 | 60 | public: 61 | /// Construct a new HTTP Server. 62 | /** @param parent Parent QObject for the server. */ 63 | QHttpServer(QObject *parent = 0); 64 | 65 | virtual ~QHttpServer(); 66 | 67 | /// Start the server by bounding to the given @c address and @c port. 68 | /** @note This function returns immediately, it does not block. 69 | @param address Address on which to listen to. Default is to listen on 70 | all interfaces which means the server can be accessed from anywhere. 71 | @param port Port number on which the server should run. 72 | @return True if the server was started successfully, false otherwise. 73 | @sa listen(quint16) */ 74 | bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 80); 75 | 76 | /// Starts the server on @c port listening on all interfaces. 77 | /** @param port Port number on which the server should run. 78 | @return True if the server was started successfully, false otherwise. 79 | @sa listen(const QHostAddress&, quint16) */ 80 | bool listen(quint16 port); 81 | 82 | /// Stop the server and listening for new connections. 83 | void close(); 84 | Q_SIGNALS: 85 | /// Emitted when a client makes a new request to the server. 86 | /** The slot should use the given @c request and @c response 87 | objects to communicate with the client. 88 | @param request New incoming request. 89 | @param response Response object to the request. */ 90 | void newRequest(QHttpRequest *request, QHttpResponse *response); 91 | 92 | private Q_SLOTS: 93 | void newConnection(); 94 | 95 | private: 96 | QTcpServer *m_tcpServer; 97 | }; 98 | 99 | #endif 100 | -------------------------------------------------------------------------------- /src/qhttpserverapi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2014 Nikhil Marathe 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #ifndef Q_HTTP_SERVER_API 24 | #define Q_HTTP_SERVER_API 25 | 26 | #include 27 | 28 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)) 29 | #ifdef Q_OS_WIN 30 | // Define to export or import depending if we are building or using the library. 31 | // QHTTPSERVER_EXPORT should only be defined when building. 32 | #if defined(QHTTPSERVER_EXPORT) 33 | #define QHTTPSERVER_API __declspec(dllexport) 34 | #else 35 | #define QHTTPSERVER_API __declspec(dllimport) 36 | #endif 37 | #else 38 | // Define empty for other platforms 39 | #define QHTTPSERVER_API 40 | #endif 41 | #else 42 | #ifdef Q_WS_WIN 43 | // Define to export or import depending if we are building or using the library. 44 | // QHTTPSERVER_EXPORT should only be defined when building. 45 | #if defined(QHTTPSERVER_EXPORT) 46 | #define QHTTPSERVER_API __declspec(dllexport) 47 | #else 48 | #define QHTTPSERVER_API __declspec(dllimport) 49 | #endif 50 | #else 51 | // Define empty for other platforms 52 | #define QHTTPSERVER_API 53 | #endif 54 | #endif 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/qhttpserverfwd.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2014 Nikhil Marathe 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 23 | #ifndef Q_HTTP_SERVER_FWD 24 | #define Q_HTTP_SERVER_FWD 25 | 26 | #include 27 | #include 28 | 29 | /*! 30 | * A map of request or response headers 31 | */ 32 | typedef QHash HeaderHash; 33 | 34 | // QHttpServer 35 | class QHttpServer; 36 | class QHttpConnection; 37 | class QHttpRequest; 38 | class QHttpResponse; 39 | 40 | // Qt 41 | class QTcpServer; 42 | class QTcpSocket; 43 | 44 | // http_parser 45 | struct http_parser_settings; 46 | struct http_parser; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/src.pro: -------------------------------------------------------------------------------- 1 | include(../qhttpserver.pri) 2 | 3 | QHTTPSERVER_BASE = .. 4 | TEMPLATE = lib 5 | 6 | TARGET = qhttpserver 7 | 8 | !win32:VERSION = 0.1.0 9 | 10 | QT += network 11 | QT -= gui 12 | 13 | CONFIG += dll debug_and_release 14 | 15 | CONFIG(debug, debug|release) { 16 | win32: TARGET = $$join(TARGET,,,d) 17 | } 18 | 19 | DEFINES += QHTTPSERVER_EXPORT 20 | 21 | INCLUDEPATH += $$QHTTPSERVER_BASE/http-parser 22 | 23 | PRIVATE_HEADERS += $$QHTTPSERVER_BASE/http-parser/http_parser.h qhttpconnection.h 24 | 25 | PUBLIC_HEADERS += qhttpserver.h qhttprequest.h qhttpresponse.h qhttpserverapi.h qhttpserverfwd.h 26 | 27 | HEADERS = $$PRIVATE_HEADERS $$PUBLIC_HEADERS 28 | SOURCES = *.cpp $$QHTTPSERVER_BASE/http-parser/http_parser.c 29 | 30 | OBJECTS_DIR = $$QHTTPSERVER_BASE/build 31 | MOC_DIR = $$QHTTPSERVER_BASE/build 32 | DESTDIR = $$QHTTPSERVER_BASE/lib 33 | 34 | target.path = $$LIBDIR 35 | headers.path = $$INCLUDEDIR 36 | headers.files = $$PUBLIC_HEADERS 37 | INSTALLS += target headers 38 | --------------------------------------------------------------------------------