Serenity Operating System
at portability 180 lines 5.2 kB view raw
1/* 2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright notice, this 9 * list of conditions and the following disclaimer. 10 * 11 * 2. Redistributions in binary form must reproduce the above copyright notice, 12 * this list of conditions and the following disclaimer in the documentation 13 * and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <AK/StringBuilder.h> 28#include <LibCore/HttpJob.h> 29#include <LibCore/HttpRequest.h> 30 31namespace Core { 32 33HttpRequest::HttpRequest() 34{ 35} 36 37HttpRequest::~HttpRequest() 38{ 39} 40 41RefPtr<NetworkJob> HttpRequest::schedule() 42{ 43 auto job = HttpJob::construct(*this); 44 job->start(); 45 return job; 46} 47 48String HttpRequest::method_name() const 49{ 50 switch (m_method) { 51 case Method::GET: 52 return "GET"; 53 case Method::HEAD: 54 return "HEAD"; 55 case Method::POST: 56 return "POST"; 57 default: 58 ASSERT_NOT_REACHED(); 59 } 60} 61 62ByteBuffer HttpRequest::to_raw_request() const 63{ 64 StringBuilder builder; 65 builder.append(method_name()); 66 builder.append(' '); 67 builder.append(m_url.path()); 68 builder.append(" HTTP/1.0\r\nHost: "); 69 builder.append(m_url.host()); 70 builder.append("\r\n\r\n"); 71 return builder.to_byte_buffer(); 72} 73 74Optional<HttpRequest> HttpRequest::from_raw_request(const ByteBuffer& raw_request) 75{ 76 enum class State { 77 InMethod, 78 InResource, 79 InProtocol, 80 InHeaderName, 81 InHeaderValue, 82 }; 83 84 State state { State::InMethod }; 85 size_t index = 0; 86 87 auto peek = [&](int offset = 0) -> u8 { 88 if (index + offset >= raw_request.size()) 89 return 0; 90 return raw_request[index + offset]; 91 }; 92 93 auto consume = [&]() -> u8 { 94 ASSERT(index < raw_request.size()); 95 return raw_request[index++]; 96 }; 97 98 Vector<u8, 256> buffer; 99 100 String method; 101 String resource; 102 String protocol; 103 Vector<Header> headers; 104 Header current_header; 105 106 auto commit_and_advance_to = [&](auto& output, State new_state) { 107 output = String::copy(buffer); 108 buffer.clear(); 109 state = new_state; 110 }; 111 112 while (index < raw_request.size()) { 113 // FIXME: Figure out what the appropriate limitations should be. 114 if (buffer.size() > 65536) 115 return {}; 116 switch (state) { 117 case State::InMethod: 118 if (peek() == ' ') { 119 consume(); 120 commit_and_advance_to(method, State::InResource); 121 break; 122 } 123 buffer.append(consume()); 124 break; 125 case State::InResource: 126 if (peek() == ' ') { 127 consume(); 128 commit_and_advance_to(resource, State::InProtocol); 129 break; 130 } 131 buffer.append(consume()); 132 break; 133 case State::InProtocol: 134 if (peek(0) == '\r' && peek(1) == '\n') { 135 consume(); 136 consume(); 137 commit_and_advance_to(protocol, State::InHeaderName); 138 break; 139 } 140 buffer.append(consume()); 141 break; 142 case State::InHeaderName: 143 if (peek(0) == ':' && peek(1) == ' ') { 144 consume(); 145 consume(); 146 commit_and_advance_to(current_header.name, State::InHeaderValue); 147 break; 148 } 149 buffer.append(consume()); 150 break; 151 case State::InHeaderValue: 152 if (peek(0) == '\r' && peek(1) == '\n') { 153 consume(); 154 consume(); 155 commit_and_advance_to(current_header.value, State::InHeaderName); 156 headers.append(move(current_header)); 157 break; 158 } 159 buffer.append(consume()); 160 break; 161 } 162 } 163 164 HttpRequest request; 165 if (method == "GET") 166 request.m_method = Method::GET; 167 else if (method == "HEAD") 168 request.m_method = Method::HEAD; 169 else if (method == "POST") 170 request.m_method = Method::POST; 171 else 172 return {}; 173 174 request.m_resource = resource; 175 request.m_headers = move(headers); 176 177 return request; 178} 179 180}