iodine 0.7.38 → 0.7.39
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +5 -12
- data/CHANGELOG.md +8 -0
- data/README.md +20 -10
- data/Rakefile +3 -1
- data/ext/iodine/extconf.rb +0 -34
- data/ext/iodine/http.c +76 -93
- data/ext/iodine/http1.c +2 -13
- data/ext/iodine/http1_parser.h +806 -63
- data/ext/iodine/iodine_defer.c +1 -0
- data/lib/iodine/connection.rb +12 -0
- data/lib/iodine/version.rb +1 -1
- metadata +4 -5
- data/ext/iodine/http1_parser.c +0 -616
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ad7a8dc81e2698056037086bfd6dd2f24ee975229a928092ac039ae9c44cb0d7
|
4
|
+
data.tar.gz: 84ff62b2c79efe4b0f5f1d796e27e6513bc8a29414fbd771404c814567d1138c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc69044b0cac72fda7be13ffb6e039007282f07e6b2607ea23e0a97733250d1b883d5e9cee171c67095e4eebd623f04f4d9d08ee2067f5a42a923b6785e3b824
|
7
|
+
data.tar.gz: b182b80a365fabaa0b97ce312f4bd2033ed28ba5a07759674cd305486f7a43ae64704da76449a84379be1a844ffe2df9396a863f4e8df5a9883d796be9fc3a91
|
@@ -9,10 +9,10 @@ assignees: ''
|
|
9
9
|
|
10
10
|
### System Information
|
11
11
|
|
12
|
-
- **OS**: [e.g. macOS 10.
|
13
|
-
- **Ruby**: [e.g. 2.
|
14
|
-
- **Version**: [e.g. 0.7.
|
15
|
-
- **OpenSSL**: [OpenSSL 1.1.
|
12
|
+
- **OS**: [e.g. macOS 10.15.4]
|
13
|
+
- **Ruby**: [e.g. 2.7.0]
|
14
|
+
- **Version**: [e.g. 0.7.38]
|
15
|
+
- **OpenSSL**: [OpenSSL 1.1.1f 20 Mar 2020]
|
16
16
|
|
17
17
|
### Description
|
18
18
|
|
@@ -37,11 +37,4 @@ A clear and concise description of what you expected to happen.
|
|
37
37
|
|
38
38
|
### Actual behavior
|
39
39
|
|
40
|
-
|
41
|
-
- Device: [e.g. iPhone6]
|
42
|
-
- OS: [e.g. iOS8.1]
|
43
|
-
- Browser [e.g. stock browser, safari]
|
44
|
-
- Version [e.g. 22]
|
45
|
-
|
46
|
-
**Additional context**
|
47
|
-
Add any other context about the problem here.
|
40
|
+
A clear and concise description of what actually happened.
|
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,14 @@ Please notice that this change log contains changes for upcoming releases as wel
|
|
6
6
|
|
7
7
|
## Changes:
|
8
8
|
|
9
|
+
#### Change log v.0.7.39 (2020-05-18)
|
10
|
+
|
11
|
+
**Security**: a request smuggling attack vector and Transfer Encoding attack vector in the HTTP/1.1 parser were exposed by Sam Sanoop from [the Snyk Security team (snyk.io)](https://snyk.io). The parser was updated to deal with these potential issues.
|
12
|
+
|
13
|
+
**Fix**: (`http`) fixes an issue with date calculation - exposed by Franck Gille (@fgi) in issue #92.
|
14
|
+
|
15
|
+
**Fix**: (`extconf`) fixes an installation concern raised by Benoit Daloze (@eregon), in issue #91. The default compiler is now used.
|
16
|
+
|
9
17
|
#### Change log v.0.7.38
|
10
18
|
|
11
19
|
**Fix**: (`http`) fixes an issue and improves support for chunked encoded payloads. Credit to Ian Ker-Seymer ( @ianks ) for exposing this, writing tests and opening both the issue #87 and the PR #88.
|
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
# iodine - a fast HTTP /
|
1
|
+
# iodine - Why Settle for a fast HTTP / WebSocket Server with native Pub/Sub?
|
2
|
+
|
2
3
|
[![Gem](https://img.shields.io/gem/dt/iodine.svg)](https://rubygems.org/gems/iodine)
|
3
4
|
[![Build Status](https://travis-ci.org/boazsegev/iodine.svg?branch=master)](https://travis-ci.org/boazsegev/iodine)
|
4
5
|
[![Gem Version](https://badge.fury.io/rb/iodine.svg)](https://badge.fury.io/rb/iodine)
|
@@ -7,28 +8,29 @@
|
|
7
8
|
|
8
9
|
[![Logo](https://github.com/boazsegev/iodine/raw/master/logo.png)](https://github.com/boazsegev/iodine)
|
9
10
|
|
10
|
-
|
11
|
+
Iodine is a fast concurrent web application server for real-time Ruby applications, with native support for WebSockets and Pub/Sub services - but it's also so much more.
|
11
12
|
|
12
|
-
|
13
|
+
Iodine is a Ruby wrapper for many of the [facil.io](https://facil.io) C framework, leveraging the speed of C for many common web application tasks. In addition, iodine abstracts away all network concerns, so you never need to worry about the transport layer, free to concentrate on your application logic.
|
13
14
|
|
14
|
-
Iodine
|
15
|
+
Iodine includes native support for:
|
15
16
|
|
16
17
|
* HTTP, WebSockets and EventSource (SSE) Services (server);
|
17
18
|
* WebSocket connections (server / client);
|
18
19
|
* Pub/Sub (with optional Redis Pub/Sub scaling);
|
19
|
-
*
|
20
|
-
*
|
20
|
+
* Fast(!) builtin Mustache template engine.
|
21
|
+
* Static file service (with automatic `gzip` support for pre-compressed assets);
|
21
22
|
* Asynchronous event scheduling and timers;
|
22
|
-
*
|
23
|
+
* HTTP/1.1 keep-alive and pipelining;
|
23
24
|
* TLS 1.2 and above (Requires OpenSSL >= 1.1.0);
|
24
25
|
* TCP/IP server and client connectivity;
|
25
26
|
* Unix Socket server and client connectivity;
|
27
|
+
* Hot Restart (using the USR1 signal);
|
26
28
|
* Custom protocol authoring;
|
27
29
|
* Optimized Logging to `stderr`.
|
28
30
|
* [Sequel](https://github.com/jeremyevans/sequel) and ActiveRecord forking protection.
|
29
31
|
* and more!
|
30
32
|
|
31
|
-
|
33
|
+
Since iodine wraps much of the [C facil.io framework](https://github.com/boazsegev/facil.io) to Ruby:
|
32
34
|
|
33
35
|
* Iodine can handle **thousands of concurrent connections** (tested with more then 20K connections on Linux)!
|
34
36
|
|
@@ -36,9 +38,9 @@ Iodine is an **evented** framework with a simple API that ports much of the [C f
|
|
36
38
|
|
37
39
|
Iodine is a C extension for Ruby, developed and optimized for Ruby MRI 2.2.2 and up... it should support the whole Ruby 2.0 MRI family, but CI tests start at Ruby 2.2.2.
|
38
40
|
|
39
|
-
**Note**: iodine does **not** support
|
41
|
+
**Note**: iodine does **not** support streaming when using Rack. It's recommended to avoid blocking the server when using `body.each` since the `each` loop will block the iodine's thread until it's finished and iodine won't send any data before the loop is done.
|
40
42
|
|
41
|
-
## Iodine - a fast & powerful HTTP +
|
43
|
+
## Iodine - a fast & powerful HTTP + WebSockets server with native Pub/Sub
|
42
44
|
|
43
45
|
Iodine includes a light and fast HTTP and Websocket server written in C that was written according to the [Rack interface specifications](http://www.rubydoc.info/github/rack/rack/master/file/SPEC) and the [Websocket draft extension](./SPEC-Websocket-Draft.md).
|
44
46
|
|
@@ -770,6 +772,14 @@ Yes, please, here are some thoughts:
|
|
770
772
|
|
771
773
|
* If you love the project or thought the code was nice, maybe helped you in your own project, drop me a line. I'd love to know.
|
772
774
|
|
775
|
+
### Running the Tests
|
776
|
+
|
777
|
+
Running this task will compile the C extensions then run RSpec tests:
|
778
|
+
|
779
|
+
```sh
|
780
|
+
bundle exec rake spec
|
781
|
+
```
|
782
|
+
|
773
783
|
## License
|
774
784
|
|
775
785
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
@@ -3,10 +3,12 @@ require "rake/extensiontask"
|
|
3
3
|
|
4
4
|
begin
|
5
5
|
require 'rspec/core/rake_task'
|
6
|
-
RSpec::Core::RakeTask.new(:
|
6
|
+
RSpec::Core::RakeTask.new(:rspec)
|
7
7
|
rescue LoadError
|
8
8
|
end
|
9
9
|
|
10
|
+
task :spec => [:compile, :rspec]
|
11
|
+
|
10
12
|
task :default => [:compile, :spec]
|
11
13
|
|
12
14
|
Rake::ExtensionTask.new "iodine" do |ext|
|
data/ext/iodine/extconf.rb
CHANGED
@@ -1,34 +1,5 @@
|
|
1
1
|
require 'mkmf'
|
2
2
|
|
3
|
-
if ENV['CC']
|
4
|
-
ENV['CPP'] ||= ENV['CC']
|
5
|
-
puts "detected user prefered compiler (#{ENV['CC']}):", `#{ENV['CC']} -v`
|
6
|
-
elsif find_executable('clang') && puts('testing clang for stdatomic support...').nil? && system("printf \"\#include <stdatomic.h>\nint main(void) {}\" | clang -include stdatomic.h -xc -o /dev/null -", out: '/dev/null')
|
7
|
-
$CC = ENV['CC'] = 'clang'
|
8
|
-
$CPP = ENV['CPP'] = 'clang'
|
9
|
-
puts "using clang compiler v. #{`clang -dumpversion`}."
|
10
|
-
elsif find_executable('gcc') && (`gcc -dumpversion 2>&1`.to_i >= 5)
|
11
|
-
$CC = ENV['CC'] = 'gcc'
|
12
|
-
$CPP = ENV['CPP'] = find_executable('g++') ? 'g++' : 'gcc'
|
13
|
-
puts "using gcc #{ `gcc -dumpversion 2>&1`.to_i }"
|
14
|
-
elsif find_executable('gcc-6')
|
15
|
-
$CC = ENV['CC'] = 'gcc-6'
|
16
|
-
$CPP = ENV['CPP'] = find_executable('g++-6') ? 'g++-6' : 'gcc-6'
|
17
|
-
puts 'using gcc-6 compiler.'
|
18
|
-
elsif find_executable('gcc-5')
|
19
|
-
$CC = ENV['CC'] = 'gcc-5'
|
20
|
-
$CPP = ENV['CPP'] = find_executable('g++-5') ? 'g++-5' : 'gcc-5'
|
21
|
-
puts 'using gcc-5 compiler.'
|
22
|
-
elsif find_executable('gcc-4.9')
|
23
|
-
$CC = ENV['CC'] = 'gcc-4.9'
|
24
|
-
$CPP = ENV['CPP'] = find_executable('g++-4.9') ? 'g++-4.9' : 'gcc-4.9'
|
25
|
-
puts 'using gcc-4.9 compiler.'
|
26
|
-
else
|
27
|
-
puts 'using an unknown (old?) compiler... who knows if this will work out... we hope.'
|
28
|
-
end
|
29
|
-
|
30
|
-
|
31
|
-
|
32
3
|
# Test polling
|
33
4
|
def iodine_test_polling_support
|
34
5
|
iodine_poll_test_kqueue = <<EOS
|
@@ -129,10 +100,5 @@ EOS
|
|
129
100
|
end
|
130
101
|
end
|
131
102
|
end
|
132
|
-
# $defs << "-DFIO_USE_RISKY_HASH"
|
133
|
-
|
134
|
-
RbConfig::MAKEFILE_CONFIG['CFLAGS'] = $CFLAGS = "-std=c11 -DFIO_PRINT_STATE=0 #{$CFLAGS} #{$CFLAGS == ENV['CFLAGS'] ? "" : ENV['CFLAGS']}"
|
135
|
-
RbConfig::MAKEFILE_CONFIG['CC'] = $CC = ENV['CC'] if ENV['CC']
|
136
|
-
RbConfig::MAKEFILE_CONFIG['CPP'] = $CPP = ENV['CPP'] if ENV['CPP']
|
137
103
|
|
138
104
|
create_makefile 'iodine/iodine'
|
data/ext/iodine/http.c
CHANGED
@@ -2089,111 +2089,94 @@ See the libc `gmtime_r` documentation for details.
|
|
2089
2089
|
|
2090
2090
|
Falls back to `gmtime_r` for dates before epoch.
|
2091
2091
|
*/
|
2092
|
-
struct tm *http_gmtime(time_t timer, struct tm *
|
2093
|
-
// static char* DAYS[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri",
|
2094
|
-
// "Sat"}; static char * Months = { "Jan", "Feb", "Mar", "Apr", "May",
|
2095
|
-
// "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
2096
|
-
static const uint8_t month_len[] = {
|
2097
|
-
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, // nonleap year
|
2098
|
-
31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 // leap year
|
2099
|
-
};
|
2100
|
-
if (timer < 0)
|
2101
|
-
return gmtime_r(&timer, tmbuf);
|
2092
|
+
struct tm *http_gmtime(time_t timer, struct tm *tm) {
|
2102
2093
|
ssize_t a, b;
|
2103
|
-
#if HAVE_TM_TM_ZONE
|
2104
|
-
*
|
2094
|
+
#if HAVE_TM_TM_ZONE || defined(BSD)
|
2095
|
+
*tm = (struct tm){
|
2105
2096
|
.tm_isdst = 0,
|
2106
|
-
.
|
2107
|
-
.tm_mon = 0,
|
2108
|
-
.tm_zone = "UTC",
|
2097
|
+
.tm_zone = (char *)"UTC",
|
2109
2098
|
};
|
2110
2099
|
#else
|
2111
|
-
*
|
2100
|
+
*tm = (struct tm){
|
2112
2101
|
.tm_isdst = 0,
|
2113
|
-
.tm_year = 70, // tm_year == The number of years since 1900
|
2114
|
-
.tm_mon = 0,
|
2115
2102
|
};
|
2116
2103
|
#endif
|
2117
|
-
|
2118
|
-
//
|
2119
|
-
|
2120
|
-
|
2121
|
-
|
2122
|
-
|
2123
|
-
|
2124
|
-
|
2125
|
-
|
2126
|
-
|
2127
|
-
|
2128
|
-
//
|
2129
|
-
|
2130
|
-
|
2131
|
-
|
2132
|
-
|
2133
|
-
|
2134
|
-
|
2135
|
-
|
2136
|
-
|
2137
|
-
|
2138
|
-
b -= DAYS_PER_100_YEARS;
|
2139
|
-
if (((tmbuf->tm_year / 100) & 3) ==
|
2140
|
-
0) // leap century divisable by 400 => add leap
|
2104
|
+
|
2105
|
+
// convert seconds from epoch to days from epoch + extract data
|
2106
|
+
if (timer >= 0) {
|
2107
|
+
// for seconds up to weekdays, we reduce the reminder every step.
|
2108
|
+
a = (ssize_t)timer;
|
2109
|
+
b = a / 60; // b == time in minutes
|
2110
|
+
tm->tm_sec = a - (b * 60);
|
2111
|
+
a = b / 60; // b == time in hours
|
2112
|
+
tm->tm_min = b - (a * 60);
|
2113
|
+
b = a / 24; // b == time in days since epoch
|
2114
|
+
tm->tm_hour = a - (b * 24);
|
2115
|
+
// b == number of days since epoch
|
2116
|
+
// day of epoch was a thursday. Add + 4 so sunday == 0...
|
2117
|
+
tm->tm_wday = (b + 4) % 7;
|
2118
|
+
} else {
|
2119
|
+
// for seconds up to weekdays, we reduce the reminder every step.
|
2120
|
+
a = (ssize_t)timer;
|
2121
|
+
b = a / 60; // b == time in minutes
|
2122
|
+
if (b * 60 != a) {
|
2123
|
+
/* seconds passed */
|
2124
|
+
tm->tm_sec = (a - (b * 60)) + 60;
|
2141
2125
|
--b;
|
2142
|
-
|
2143
|
-
|
2144
|
-
|
2145
|
-
while (b >= DAYS_PER_32_YEARS) {
|
2146
|
-
tmbuf->tm_year += 32;
|
2147
|
-
b -= DAYS_PER_32_YEARS;
|
2148
|
-
}
|
2149
|
-
#undef DAYS_PER_32_YEARS
|
2150
|
-
#define DAYS_PER_8_YEARS ((8 * 365) + 2)
|
2151
|
-
while (b >= DAYS_PER_8_YEARS) {
|
2152
|
-
tmbuf->tm_year += 8;
|
2153
|
-
b -= DAYS_PER_8_YEARS;
|
2154
|
-
}
|
2155
|
-
#undef DAYS_PER_8_YEARS
|
2156
|
-
#define DAYS_PER_4_YEARS ((4 * 365) + 1)
|
2157
|
-
while (b >= DAYS_PER_4_YEARS) {
|
2158
|
-
tmbuf->tm_year += 4;
|
2159
|
-
b -= DAYS_PER_4_YEARS;
|
2160
|
-
}
|
2161
|
-
#undef DAYS_PER_4_YEARS
|
2162
|
-
while (b >= 365) {
|
2163
|
-
tmbuf->tm_year += 1;
|
2164
|
-
b -= 365;
|
2165
|
-
if ((tmbuf->tm_year & 3) == 0) { // leap year
|
2166
|
-
if (b > 0) {
|
2167
|
-
--b;
|
2168
|
-
continue;
|
2169
|
-
} else {
|
2170
|
-
b += 365;
|
2171
|
-
--tmbuf->tm_year;
|
2172
|
-
break;
|
2173
|
-
}
|
2126
|
+
} else {
|
2127
|
+
/* no seconds */
|
2128
|
+
tm->tm_sec = 0;
|
2174
2129
|
}
|
2175
|
-
|
2176
|
-
|
2177
|
-
|
2178
|
-
|
2179
|
-
|
2180
|
-
|
2181
|
-
|
2182
|
-
|
2183
|
-
b -= month_len[i];
|
2184
|
-
++tmbuf->tm_mon;
|
2130
|
+
a = b / 60; // b == time in hours
|
2131
|
+
if (a * 60 != b) {
|
2132
|
+
/* minutes passed */
|
2133
|
+
tm->tm_min = (b - (a * 60)) + 60;
|
2134
|
+
--a;
|
2135
|
+
} else {
|
2136
|
+
/* no minutes */
|
2137
|
+
tm->tm_min = 0;
|
2185
2138
|
}
|
2186
|
-
|
2187
|
-
|
2188
|
-
|
2189
|
-
|
2190
|
-
|
2191
|
-
|
2192
|
-
|
2139
|
+
b = a / 24; // b == time in days since epoch?
|
2140
|
+
if (b * 24 != a) {
|
2141
|
+
/* hours passed */
|
2142
|
+
tm->tm_hour = (a - (b * 24)) + 24;
|
2143
|
+
--b;
|
2144
|
+
} else {
|
2145
|
+
/* no hours */
|
2146
|
+
tm->tm_hour = 0;
|
2193
2147
|
}
|
2148
|
+
// day of epoch was a thursday. Add + 4 so sunday == 0...
|
2149
|
+
tm->tm_wday = ((b - 3) % 7);
|
2150
|
+
if (tm->tm_wday)
|
2151
|
+
tm->tm_wday += 7;
|
2152
|
+
/* b == days from epoch */
|
2194
2153
|
}
|
2195
|
-
|
2196
|
-
|
2154
|
+
|
2155
|
+
// at this point we can apply the algorithm described here:
|
2156
|
+
// http://howardhinnant.github.io/date_algorithms.html#civil_from_days
|
2157
|
+
// Credit to Howard Hinnant.
|
2158
|
+
{
|
2159
|
+
b += 719468L; // adjust to March 1st, 2000 (post leap of 400 year era)
|
2160
|
+
// 146,097 = days in era (400 years)
|
2161
|
+
const size_t era = (b >= 0 ? b : b - 146096) / 146097;
|
2162
|
+
const uint32_t doe = (b - (era * 146097)); // day of era
|
2163
|
+
const uint16_t yoe =
|
2164
|
+
(doe - doe / 1460 + doe / 36524 - doe / 146096) / 365; // year of era
|
2165
|
+
a = yoe;
|
2166
|
+
a += era * 400; // a == year number, assuming year starts on March 1st...
|
2167
|
+
const uint16_t doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
2168
|
+
const uint16_t mp = (5U * doy + 2) / 153;
|
2169
|
+
const uint16_t d = doy - (153U * mp + 2) / 5 + 1;
|
2170
|
+
const uint8_t m = mp + (mp < 10 ? 2 : -10);
|
2171
|
+
a += (m <= 1);
|
2172
|
+
tm->tm_year = a - 1900; // tm_year == years since 1900
|
2173
|
+
tm->tm_mon = m;
|
2174
|
+
tm->tm_mday = d;
|
2175
|
+
const uint8_t is_leap = (a % 4 == 0 && (a % 100 != 0 || a % 400 == 0));
|
2176
|
+
tm->tm_yday = (doy + (is_leap) + 28 + 31) % (365 + is_leap);
|
2177
|
+
}
|
2178
|
+
|
2179
|
+
return tm;
|
2197
2180
|
}
|
2198
2181
|
|
2199
2182
|
static const char *DAY_NAMES[] = {"Sun", "Mon", "Tue", "Wed",
|
data/ext/iodine/http1.c
CHANGED
@@ -599,8 +599,7 @@ static int http1_on_query(http1_parser_s *parser, char *query, size_t len) {
|
|
599
599
|
return 0;
|
600
600
|
}
|
601
601
|
/** called when a the HTTP/1.x version is parsed. */
|
602
|
-
static int
|
603
|
-
size_t len) {
|
602
|
+
static int http1_on_version(http1_parser_s *parser, char *version, size_t len) {
|
604
603
|
http1_pr2handle(parser2http(parser)).version = fiobj_str_new(version, len);
|
605
604
|
parser2http(parser)->header_size += len;
|
606
605
|
/* start counting - occurs on the first line of both requests and responses */
|
@@ -687,17 +686,7 @@ static inline void http1_consume_data(intptr_t uuid, http1pr_s *p) {
|
|
687
686
|
if (!p->buf_len)
|
688
687
|
return;
|
689
688
|
do {
|
690
|
-
i =
|
691
|
-
.buffer = p->buf + (org_len - p->buf_len),
|
692
|
-
.length = p->buf_len, .on_request = http1_on_request,
|
693
|
-
.on_response = http1_on_response,
|
694
|
-
.on_method = http1_on_method,
|
695
|
-
.on_status = http1_on_status, .on_path = http1_on_path,
|
696
|
-
.on_query = http1_on_query,
|
697
|
-
.on_http_version = http1_on_http_version,
|
698
|
-
.on_header = http1_on_header,
|
699
|
-
.on_body_chunk = http1_on_body_chunk,
|
700
|
-
.on_error = http1_on_error);
|
689
|
+
i = http1_parse(&p->parser, p->buf + (org_len - p->buf_len), p->buf_len);
|
701
690
|
p->buf_len -= i;
|
702
691
|
--pipeline_limit;
|
703
692
|
} while (i && p->buf_len && pipeline_limit && !p->stop);
|
data/ext/iodine/http1_parser.h
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#ifndef H_HTTP1_PARSER_H
|
2
2
|
/*
|
3
|
-
Copyright: Boaz Segev, 2017-
|
3
|
+
Copyright: Boaz Segev, 2017-2020
|
4
4
|
License: MIT
|
5
5
|
|
6
6
|
Feel free to copy, use and enjoy according to the license provided.
|
@@ -9,29 +9,32 @@ Feel free to copy, use and enjoy according to the license provided.
|
|
9
9
|
/**
|
10
10
|
This is a callback based parser. It parses the skeleton of the HTTP/1.x protocol
|
11
11
|
and leaves most of the work (validation, error checks, etc') to the callbacks.
|
12
|
-
|
13
|
-
This is an attempt to replace the existing HTTP/1.x parser with something easier
|
14
|
-
to maintain and that could be used for an HTTP/1.x client as well.
|
15
12
|
*/
|
16
13
|
#define H_HTTP1_PARSER_H
|
17
14
|
#include <stddef.h>
|
18
15
|
#include <stdint.h>
|
19
16
|
#include <stdio.h>
|
20
17
|
#include <stdlib.h>
|
18
|
+
#include <string.h>
|
21
19
|
#include <sys/types.h>
|
22
20
|
|
21
|
+
/* *****************************************************************************
|
22
|
+
Parser Settings
|
23
|
+
***************************************************************************** */
|
24
|
+
|
23
25
|
#ifndef HTTP_HEADERS_LOWERCASE
|
24
26
|
/**
|
25
|
-
*
|
27
|
+
* When defined, HTTP headers will be converted to lowercase and header
|
26
28
|
* searches will be case sensitive.
|
27
29
|
*
|
28
|
-
*
|
30
|
+
* This is highly recommended, required by facil.io and helps with HTTP/2
|
31
|
+
* compatibility.
|
29
32
|
*/
|
30
33
|
#define HTTP_HEADERS_LOWERCASE 1
|
31
34
|
#endif
|
32
35
|
|
33
|
-
#ifndef
|
34
|
-
#define
|
36
|
+
#ifndef HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
|
37
|
+
#define HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING 1
|
35
38
|
#endif
|
36
39
|
|
37
40
|
#ifndef FIO_MEMCHAR
|
@@ -39,65 +42,727 @@ to maintain and that could be used for an HTTP/1.x client as well.
|
|
39
42
|
#define FIO_MEMCHAR 0
|
40
43
|
#endif
|
41
44
|
|
42
|
-
#
|
45
|
+
#ifndef ALLOW_UNALIGNED_MEMORY_ACCESS
|
46
|
+
/** Peforms some optimizations assuming unaligned memory access is okay. */
|
47
|
+
#define ALLOW_UNALIGNED_MEMORY_ACCESS 0
|
48
|
+
#endif
|
43
49
|
|
44
|
-
#
|
45
|
-
|
46
|
-
#else
|
47
|
-
#define HEADER_NAME_IS_EQ(var_name, const_name, len) \
|
48
|
-
(!strncasecmp((var_name), (const_name), (len)))
|
50
|
+
#ifndef HTTP1_PARSER_CONVERT_EOL2NUL
|
51
|
+
#define HTTP1_PARSER_CONVERT_EOL2NUL 0
|
49
52
|
#endif
|
50
53
|
|
54
|
+
/* *****************************************************************************
|
55
|
+
Parser API
|
56
|
+
***************************************************************************** */
|
57
|
+
|
51
58
|
/** this struct contains the state of the parser. */
|
52
59
|
typedef struct http1_parser_s {
|
53
|
-
void *udata;
|
54
60
|
struct http1_parser_protected_read_only_state_s {
|
55
|
-
|
56
|
-
ssize_t read;
|
61
|
+
long long content_length; /* negative values indicate chuncked data state */
|
62
|
+
ssize_t read; /* total number of bytes read so far (body only) */
|
57
63
|
uint8_t *next; /* the known position for the end of request/response */
|
58
64
|
uint8_t reserved; /* for internal use */
|
59
65
|
} state;
|
60
66
|
} http1_parser_s;
|
61
67
|
|
68
|
+
#define HTTP1_PARSER_INIT \
|
69
|
+
{ \
|
70
|
+
{ 0 } \
|
71
|
+
}
|
72
|
+
|
73
|
+
/**
|
74
|
+
* Returns the amount of data actually consumed by the parser.
|
75
|
+
*
|
76
|
+
* The value 0 indicates there wasn't enough data to be parsed and the same
|
77
|
+
* buffer (with more data) should be resubmitted.
|
78
|
+
*
|
79
|
+
* A value smaller than the buffer size indicates that EITHER a request /
|
80
|
+
* response was detected OR that the leftover could not be consumed because more
|
81
|
+
* data was required.
|
82
|
+
*
|
83
|
+
* Simply resubmit the reminder of the data to continue parsing.
|
84
|
+
*
|
85
|
+
* A request / response callback automatically stops the parsing process,
|
86
|
+
* allowing the user to adjust or refresh the state of the data.
|
87
|
+
*/
|
88
|
+
static size_t http1_parse(http1_parser_s *parser, void *buffer, size_t length);
|
89
|
+
|
90
|
+
/* *****************************************************************************
|
91
|
+
Required Callbacks (MUST be implemented by including file)
|
92
|
+
***************************************************************************** */
|
93
|
+
|
94
|
+
/** called when a request was received. */
|
95
|
+
static int http1_on_request(http1_parser_s *parser);
|
96
|
+
/** called when a response was received. */
|
97
|
+
static int http1_on_response(http1_parser_s *parser);
|
98
|
+
/** called when a request method is parsed. */
|
99
|
+
static int http1_on_method(http1_parser_s *parser, char *method,
|
100
|
+
size_t method_len);
|
101
|
+
/** called when a response status is parsed. the status_str is the string
|
102
|
+
* without the prefixed numerical status indicator.*/
|
103
|
+
static int http1_on_status(http1_parser_s *parser, size_t status,
|
104
|
+
char *status_str, size_t len);
|
105
|
+
/** called when a request path (excluding query) is parsed. */
|
106
|
+
static int http1_on_path(http1_parser_s *parser, char *path, size_t path_len);
|
107
|
+
/** called when a request path (excluding query) is parsed. */
|
108
|
+
static int http1_on_query(http1_parser_s *parser, char *query,
|
109
|
+
size_t query_len);
|
110
|
+
/** called when a the HTTP/1.x version is parsed. */
|
111
|
+
static int http1_on_version(http1_parser_s *parser, char *version, size_t len);
|
112
|
+
/** called when a header is parsed. */
|
113
|
+
static int http1_on_header(http1_parser_s *parser, char *name, size_t name_len,
|
114
|
+
char *data, size_t data_len);
|
115
|
+
/** called when a body chunk is parsed. */
|
116
|
+
static int http1_on_body_chunk(http1_parser_s *parser, char *data,
|
117
|
+
size_t data_len);
|
118
|
+
/** called when a protocol error occurred. */
|
119
|
+
static int http1_on_error(http1_parser_s *parser);
|
120
|
+
|
121
|
+
/* *****************************************************************************
|
122
|
+
|
123
|
+
|
124
|
+
|
125
|
+
|
126
|
+
|
127
|
+
|
128
|
+
|
129
|
+
|
130
|
+
|
131
|
+
|
132
|
+
|
133
|
+
|
134
|
+
|
135
|
+
|
136
|
+
|
137
|
+
|
138
|
+
|
139
|
+
Implementation Details
|
140
|
+
|
141
|
+
|
142
|
+
|
143
|
+
|
144
|
+
|
145
|
+
|
146
|
+
|
147
|
+
|
148
|
+
|
149
|
+
|
150
|
+
|
151
|
+
|
152
|
+
|
153
|
+
|
154
|
+
|
155
|
+
|
156
|
+
|
157
|
+
***************************************************************************** */
|
158
|
+
|
159
|
+
#if HTTP_HEADERS_LOWERCASE
|
160
|
+
#define HEADER_NAME_IS_EQ(var_name, const_name, len) \
|
161
|
+
(!memcmp((var_name), (const_name), (len)))
|
162
|
+
#else
|
163
|
+
#define HEADER_NAME_IS_EQ(var_name, const_name, len) \
|
164
|
+
(!strncasecmp((var_name), (const_name), (len)))
|
165
|
+
#endif
|
166
|
+
|
167
|
+
#define HTTP1_P_FLAG_STATUS_LINE 1
|
168
|
+
#define HTTP1_P_FLAG_HEADER_COMPLETE 2
|
169
|
+
#define HTTP1_P_FLAG_COMPLETE 4
|
170
|
+
#define HTTP1_P_FLAG_CLENGTH 8
|
171
|
+
#define HTTP1_PARSER_BIT_16 16
|
172
|
+
#define HTTP1_PARSER_BIT_32 32
|
173
|
+
#define HTTP1_P_FLAG_CHUNKED 64
|
174
|
+
#define HTTP1_P_FLAG_RESPONSE 128
|
175
|
+
|
176
|
+
/* *****************************************************************************
|
177
|
+
Seeking for characters in a string
|
178
|
+
***************************************************************************** */
|
179
|
+
|
180
|
+
#if FIO_MEMCHAR
|
181
|
+
|
62
182
|
/**
|
63
|
-
*
|
183
|
+
* This seems to be faster on some systems, especially for smaller distances.
|
64
184
|
*
|
65
|
-
*
|
185
|
+
* On newer systems, `memchr` should be faster.
|
66
186
|
*/
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
187
|
+
static int seek2ch(uint8_t **buffer, register uint8_t *const limit,
|
188
|
+
const uint8_t c) {
|
189
|
+
if (*buffer >= limit)
|
190
|
+
return 0;
|
191
|
+
if (**buffer == c) {
|
192
|
+
return 1;
|
193
|
+
}
|
194
|
+
|
195
|
+
#if !HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED
|
196
|
+
/* too short for this mess */
|
197
|
+
if ((uintptr_t)limit <= 16 + ((uintptr_t)*buffer & (~(uintptr_t)7)))
|
198
|
+
goto finish;
|
199
|
+
|
200
|
+
/* align memory */
|
201
|
+
{
|
202
|
+
const uint8_t *alignment =
|
203
|
+
(uint8_t *)(((uintptr_t)(*buffer) & (~(uintptr_t)7)) + 8);
|
204
|
+
if (*buffer < alignment)
|
205
|
+
*buffer += 1; /* we already tested this char */
|
206
|
+
if (limit >= alignment) {
|
207
|
+
while (*buffer < alignment) {
|
208
|
+
if (**buffer == c) {
|
209
|
+
return 1;
|
210
|
+
}
|
211
|
+
*buffer += 1;
|
212
|
+
}
|
213
|
+
}
|
214
|
+
}
|
215
|
+
const uint8_t *limit64 = (uint8_t *)((uintptr_t)limit & (~(uintptr_t)7));
|
216
|
+
#else
|
217
|
+
const uint8_t *limit64 = (uint8_t *)limit - 7;
|
218
|
+
#endif
|
219
|
+
uint64_t wanted1 = 0x0101010101010101ULL * c;
|
220
|
+
for (; *buffer < limit64; *buffer += 8) {
|
221
|
+
const uint64_t eq1 = ~((*((uint64_t *)*buffer)) ^ wanted1);
|
222
|
+
const uint64_t t0 = (eq1 & 0x7f7f7f7f7f7f7f7fllu) + 0x0101010101010101llu;
|
223
|
+
const uint64_t t1 = (eq1 & 0x8080808080808080llu);
|
224
|
+
if ((t0 & t1)) {
|
225
|
+
break;
|
226
|
+
}
|
227
|
+
}
|
228
|
+
#if !HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED
|
229
|
+
finish:
|
230
|
+
#endif
|
231
|
+
while (*buffer < limit) {
|
232
|
+
if (**buffer == c) {
|
233
|
+
return 1;
|
234
|
+
}
|
235
|
+
(*buffer)++;
|
236
|
+
}
|
237
|
+
return 0;
|
238
|
+
}
|
239
|
+
|
240
|
+
#else
|
241
|
+
|
242
|
+
/* a helper that seeks any char, converts it to NUL and returns 1 if found. */
|
243
|
+
inline static uint8_t seek2ch(uint8_t **pos, uint8_t *const limit, uint8_t ch) {
|
244
|
+
/* This is library based alternative that is sometimes slower */
|
245
|
+
if (*pos >= limit)
|
246
|
+
return 0;
|
247
|
+
if (**pos == ch) {
|
248
|
+
return 1;
|
249
|
+
}
|
250
|
+
uint8_t *tmp = memchr(*pos, ch, limit - (*pos));
|
251
|
+
if (tmp) {
|
252
|
+
*pos = tmp;
|
253
|
+
return 1;
|
254
|
+
}
|
255
|
+
*pos = limit;
|
256
|
+
return 0;
|
257
|
+
}
|
258
|
+
|
259
|
+
#endif
|
260
|
+
|
261
|
+
/* a helper that seeks the EOL, converts it to NUL and returns it's length */
|
262
|
+
inline static uint8_t seek2eol(uint8_t **pos, uint8_t *const limit) {
|
263
|
+
/* single char lookup using memchr might be better when target is far... */
|
264
|
+
if (!seek2ch(pos, limit, '\n'))
|
265
|
+
return 0;
|
266
|
+
if ((*pos)[-1] == '\r') {
|
267
|
+
#if HTTP1_PARSER_CONVERT_EOL2NUL
|
268
|
+
(*pos)[-1] = (*pos)[0] = 0;
|
269
|
+
#endif
|
270
|
+
return 2;
|
271
|
+
}
|
272
|
+
#if HTTP1_PARSER_CONVERT_EOL2NUL
|
273
|
+
(*pos)[0] = 0;
|
274
|
+
#endif
|
275
|
+
return 1;
|
276
|
+
}
|
277
|
+
|
278
|
+
/* *****************************************************************************
|
279
|
+
Change a letter to lower case (latin only)
|
280
|
+
***************************************************************************** */
|
281
|
+
|
282
|
+
static uint8_t http_tolower(uint8_t c) {
|
283
|
+
if (c >= 'A' && c <= 'Z')
|
284
|
+
c |= 32;
|
285
|
+
return c;
|
286
|
+
}
|
287
|
+
|
288
|
+
/* *****************************************************************************
|
289
|
+
String to Number
|
290
|
+
***************************************************************************** */
|
291
|
+
|
292
|
+
/** Converts a String to a number using base 10 */
|
293
|
+
static long long http1_atol(const uint8_t *buf, const uint8_t **end) {
|
294
|
+
register unsigned long long i = 0;
|
295
|
+
uint8_t inv = 0;
|
296
|
+
while (*buf == ' ' || *buf == '\t' || *buf == '\f')
|
297
|
+
++buf;
|
298
|
+
while (*buf == '-' || *buf == '+')
|
299
|
+
inv ^= (*(buf++) == '-');
|
300
|
+
while (i <= ((((~0ULL) >> 1) / 10)) && *buf >= '0' && *buf <= '9') {
|
301
|
+
i = i * 10;
|
302
|
+
i += *buf - '0';
|
303
|
+
++buf;
|
304
|
+
}
|
305
|
+
/* test for overflow */
|
306
|
+
if (i >= (~((~0ULL) >> 1)) || (*buf >= '0' && *buf <= '9'))
|
307
|
+
i = (~0ULL >> 1);
|
308
|
+
if (inv)
|
309
|
+
i = 0ULL - i;
|
310
|
+
if (end)
|
311
|
+
*end = buf;
|
312
|
+
return i;
|
313
|
+
}
|
314
|
+
|
315
|
+
/** Converts a String to a number using base 16, overflow limited to 113bytes */
|
316
|
+
static long long http1_atol16(const uint8_t *buf, const uint8_t **end) {
|
317
|
+
register unsigned long long i = 0;
|
318
|
+
uint8_t inv = 0;
|
319
|
+
for (int limit_ = 0;
|
320
|
+
(*buf == ' ' || *buf == '\t' || *buf == '\f') && limit_ < 32; ++limit_)
|
321
|
+
++buf;
|
322
|
+
for (int limit_ = 0; (*buf == '-' || *buf == '+') && limit_ < 32; ++limit_)
|
323
|
+
inv ^= (*(buf++) == '-');
|
324
|
+
if (*buf == '0')
|
325
|
+
++buf;
|
326
|
+
if ((*buf | 32) == 'x')
|
327
|
+
++buf;
|
328
|
+
for (int limit_ = 0; (*buf == '0') && limit_ < 32; ++limit_)
|
329
|
+
++buf;
|
330
|
+
while (!(i & (~((~(0ULL)) >> 4)))) {
|
331
|
+
if (*buf >= '0' && *buf <= '9') {
|
332
|
+
i <<= 4;
|
333
|
+
i |= *buf - '0';
|
334
|
+
} else if ((*buf | 32) >= 'a' && (*buf | 32) <= 'f') {
|
335
|
+
i <<= 4;
|
336
|
+
i |= (*buf | 32) - ('a' - 10);
|
337
|
+
} else
|
338
|
+
break;
|
339
|
+
++buf;
|
340
|
+
}
|
341
|
+
if (inv)
|
342
|
+
i = 0ULL - i;
|
343
|
+
if (end)
|
344
|
+
*end = buf;
|
345
|
+
return i;
|
346
|
+
}
|
347
|
+
|
348
|
+
/* *****************************************************************************
|
349
|
+
HTTP/1.1 parsre stages
|
350
|
+
***************************************************************************** */
|
351
|
+
|
352
|
+
inline static int http1_consume_response_line(http1_parser_s *parser,
|
353
|
+
uint8_t *start, uint8_t *end) {
|
354
|
+
parser->state.reserved |= HTTP1_P_FLAG_RESPONSE;
|
355
|
+
uint8_t *tmp = start;
|
356
|
+
if (!seek2ch(&tmp, end, ' '))
|
357
|
+
return -1;
|
358
|
+
if (http1_on_version(parser, (char *)start, tmp - start))
|
359
|
+
return -1;
|
360
|
+
tmp = start = tmp + 1;
|
361
|
+
if (!seek2ch(&tmp, end, ' '))
|
362
|
+
return -1;
|
363
|
+
if (http1_on_status(parser, http1_atol(start, NULL), (char *)(tmp + 1),
|
364
|
+
end - tmp))
|
365
|
+
return -1;
|
366
|
+
return 0;
|
367
|
+
}
|
368
|
+
|
369
|
+
inline static int http1_consume_request_line(http1_parser_s *parser,
|
370
|
+
uint8_t *start, uint8_t *end) {
|
371
|
+
uint8_t *tmp = start;
|
372
|
+
uint8_t *host_start = NULL;
|
373
|
+
uint8_t *host_end = NULL;
|
374
|
+
if (!seek2ch(&tmp, end, ' '))
|
375
|
+
return -1;
|
376
|
+
if (http1_on_method(parser, (char *)start, tmp - start))
|
377
|
+
return -1;
|
378
|
+
tmp = start = tmp + 1;
|
379
|
+
if (start[0] == 'h' && start[1] == 't' && start[2] == 't' &&
|
380
|
+
start[3] == 'p') {
|
381
|
+
if (start[4] == ':' && start[5] == '/' && start[6] == '/') {
|
382
|
+
/* Request URI is in long form... emulate Host header instead. */
|
383
|
+
tmp = host_end = host_start = (start += 7);
|
384
|
+
} else if (start[4] == 's' && start[5] == ':' && start[6] == '/' &&
|
385
|
+
start[7] == '/') {
|
386
|
+
/* Secure request is in long form... emulate Host header instead. */
|
387
|
+
tmp = host_end = host_start = (start += 8);
|
388
|
+
} else
|
389
|
+
goto review_path;
|
390
|
+
if (!seek2ch(&tmp, end, ' '))
|
391
|
+
return -1;
|
392
|
+
*tmp = ' ';
|
393
|
+
if (!seek2ch(&host_end, tmp, '/')) {
|
394
|
+
if (http1_on_path(parser, (char *)"/", 1))
|
395
|
+
return -1;
|
396
|
+
goto start_version;
|
397
|
+
}
|
398
|
+
host_end[0] = '/';
|
399
|
+
start = host_end;
|
400
|
+
}
|
401
|
+
review_path:
|
402
|
+
tmp = start;
|
403
|
+
if (seek2ch(&tmp, end, '?')) {
|
404
|
+
if (http1_on_path(parser, (char *)start, tmp - start))
|
405
|
+
return -1;
|
406
|
+
tmp = start = tmp + 1;
|
407
|
+
if (!seek2ch(&tmp, end, ' '))
|
408
|
+
return -1;
|
409
|
+
if (tmp - start > 0 && http1_on_query(parser, (char *)start, tmp - start))
|
410
|
+
return -1;
|
411
|
+
} else {
|
412
|
+
tmp = start;
|
413
|
+
if (!seek2ch(&tmp, end, ' '))
|
414
|
+
return -1;
|
415
|
+
if (http1_on_path(parser, (char *)start, tmp - start))
|
416
|
+
return -1;
|
417
|
+
}
|
418
|
+
start_version:
|
419
|
+
start = tmp + 1;
|
420
|
+
if (start + 5 >= end) /* require "HTTP/" */
|
421
|
+
return -1;
|
422
|
+
if (http1_on_version(parser, (char *)start, end - start))
|
423
|
+
return -1;
|
424
|
+
/* */
|
425
|
+
if (host_start && http1_on_header(parser, (char *)"host", 4,
|
426
|
+
(char *)host_start, host_end - host_start))
|
427
|
+
return -1;
|
428
|
+
return 0;
|
429
|
+
}
|
430
|
+
|
431
|
+
#ifndef HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER
|
432
|
+
inline /* inline the function of it's short enough */
|
433
|
+
#endif
|
434
|
+
static int
|
435
|
+
http1_consume_header_transfer_encoding(http1_parser_s *parser,
|
436
|
+
uint8_t *start, uint8_t *end_name,
|
437
|
+
uint8_t *start_value, uint8_t *end) {
|
438
|
+
/* this removes the `chunked` marker and prepares to "unchunk" the data */
|
439
|
+
while (start_value < end && (end[-1] == ',' || end[-1] == ' '))
|
440
|
+
--end;
|
441
|
+
if ((end - start_value) == 7 &&
|
442
|
+
#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED
|
443
|
+
(((uint32_t *)(start_value))[0] | 0x20202020) ==
|
444
|
+
((uint32_t *)"chun")[0] &&
|
445
|
+
(((uint32_t *)(start_value + 3))[0] | 0x20202020) ==
|
446
|
+
((uint32_t *)"nked")[0]
|
447
|
+
#else
|
448
|
+
((start_value[0] | 32) == 'c' && (start_value[1] | 32) == 'h' &&
|
449
|
+
(start_value[2] | 32) == 'u' && (start_value[3] | 32) == 'n' &&
|
450
|
+
(start_value[4] | 32) == 'k' && (start_value[5] | 32) == 'e' &&
|
451
|
+
(start_value[6] | 32) == 'd')
|
452
|
+
#endif
|
453
|
+
) {
|
454
|
+
/* simple case,only `chunked` as a value */
|
455
|
+
parser->state.reserved |= HTTP1_P_FLAG_CHUNKED;
|
456
|
+
parser->state.content_length = 0;
|
457
|
+
start_value += 7;
|
458
|
+
while (start_value < end && (*start_value == ',' || *start_value == ' '))
|
459
|
+
++start_value;
|
460
|
+
if (!(end - start_value))
|
461
|
+
return 0;
|
462
|
+
} else if ((end - start_value) > 7 &&
|
463
|
+
((end[(-7 + 0)] | 32) == 'c' && (end[(-7 + 1)] | 32) == 'h' &&
|
464
|
+
(end[(-7 + 2)] | 32) == 'u' && (end[(-7 + 3)] | 32) == 'n' &&
|
465
|
+
(end[(-7 + 4)] | 32) == 'k' && (end[(-7 + 5)] | 32) == 'e' &&
|
466
|
+
(end[(-7 + 6)] | 32) == 'd')) {
|
467
|
+
/* simple case,`chunked` at the end of list (RFC required) */
|
468
|
+
parser->state.reserved |= HTTP1_P_FLAG_CHUNKED;
|
469
|
+
parser->state.content_length = 0;
|
470
|
+
end -= 7;
|
471
|
+
while (start_value < end && (end[-1] == ',' || end[-1] == ' '))
|
472
|
+
--end;
|
473
|
+
if (!(end - start_value))
|
474
|
+
return 0;
|
475
|
+
}
|
476
|
+
#ifdef HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER /* RFC diisallows this */
|
477
|
+
else if ((end - start_value) > 7 && (end - start_value) < 256) {
|
478
|
+
/* complex case, `the, chunked, marker, is in the middle of list */
|
479
|
+
uint8_t val[256];
|
480
|
+
size_t val_len = 0;
|
481
|
+
while (start_value < end && val_len < 256) {
|
482
|
+
if ((end - start_value) >= 7) {
|
483
|
+
if (
|
484
|
+
#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED
|
485
|
+
(((uint32_t *)(start_value))[0] | 0x20202020) ==
|
486
|
+
((uint32_t *)"chun")[0] &&
|
487
|
+
(((uint32_t *)(start_value + 3))[0] | 0x20202020) ==
|
488
|
+
((uint32_t *)"nked")[0]
|
489
|
+
#else
|
490
|
+
((start_value[0] | 32) == 'c' && (start_value[1] | 32) == 'h' &&
|
491
|
+
(start_value[2] | 32) == 'u' && (start_value[3] | 32) == 'n' &&
|
492
|
+
(start_value[4] | 32) == 'k' && (start_value[5] | 32) == 'e' &&
|
493
|
+
(start_value[6] | 32) == 'd')
|
494
|
+
#endif
|
495
|
+
|
496
|
+
) {
|
497
|
+
parser->state.reserved |= HTTP1_P_FLAG_CHUNKED;
|
498
|
+
parser->state.content_length = 0;
|
499
|
+
start_value += 7;
|
500
|
+
/* skip comma / white space */
|
501
|
+
while (start_value < end &&
|
502
|
+
(*start_value == ',' || *start_value == ' '))
|
503
|
+
++start_value;
|
504
|
+
continue;
|
505
|
+
}
|
506
|
+
}
|
507
|
+
/* copy value */
|
508
|
+
while (start_value < end && val_len < 256 && start_value[0] != ',') {
|
509
|
+
val[val_len++] = *start_value;
|
510
|
+
++start_value;
|
511
|
+
}
|
512
|
+
/* copy comma */
|
513
|
+
if (start_value[0] == ',' && val_len < 256) {
|
514
|
+
val[val_len++] = *start_value;
|
515
|
+
++start_value;
|
516
|
+
}
|
517
|
+
/* skip spaces */
|
518
|
+
while (start_value < end && start_value[0] == ' ') {
|
519
|
+
++start_value;
|
520
|
+
}
|
521
|
+
}
|
522
|
+
if (val_len < 256) {
|
523
|
+
while (start_value < end && val_len < 256) {
|
524
|
+
val[val_len++] = *start_value;
|
525
|
+
++start_value;
|
526
|
+
}
|
527
|
+
val[val_len] = 0;
|
528
|
+
}
|
529
|
+
/* perform callback with `val` or indicate error */
|
530
|
+
if (val_len == 256 ||
|
531
|
+
(val_len && http1_on_header(parser, (char *)start, (end_name - start),
|
532
|
+
(char *)val, val_len)))
|
533
|
+
return -1;
|
534
|
+
return 0;
|
535
|
+
}
|
536
|
+
#endif /* HTTP1_ALLOW_CHUNKED_IN_MIDDLE_OF_HEADER */
|
537
|
+
/* perform callback */
|
538
|
+
if (http1_on_header(parser, (char *)start, (end_name - start),
|
539
|
+
(char *)start_value, end - start_value))
|
540
|
+
return -1;
|
541
|
+
return 0;
|
542
|
+
}
|
543
|
+
inline static int http1_consume_header_top(http1_parser_s *parser,
|
544
|
+
uint8_t *start, uint8_t *end_name,
|
545
|
+
uint8_t *start_value, uint8_t *end) {
|
546
|
+
if ((end_name - start) == 14 &&
|
547
|
+
#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED && HTTP_HEADERS_LOWERCASE
|
548
|
+
*((uint64_t *)start) == *((uint64_t *)"content-") &&
|
549
|
+
*((uint64_t *)(start + 6)) == *((uint64_t *)"t-length")
|
550
|
+
#else
|
551
|
+
HEADER_NAME_IS_EQ((char *)start, "content-length", 14)
|
552
|
+
#endif
|
553
|
+
) {
|
554
|
+
/* handle the special `content-length` header */
|
555
|
+
if ((parser->state.reserved & HTTP1_P_FLAG_CHUNKED))
|
556
|
+
return 0; /* ignore if `chunked` */
|
557
|
+
long long old_clen = parser->state.content_length;
|
558
|
+
parser->state.content_length = http1_atol(start_value, NULL);
|
559
|
+
if ((parser->state.reserved & HTTP1_P_FLAG_CLENGTH) &&
|
560
|
+
old_clen != parser->state.content_length) {
|
561
|
+
/* content-length header repeated with conflict */
|
562
|
+
return -1;
|
563
|
+
}
|
564
|
+
parser->state.reserved |= HTTP1_P_FLAG_CLENGTH;
|
565
|
+
} else if ((end_name - start) == 17 && (end - start_value) >= 7 &&
|
566
|
+
!parser->state.content_length &&
|
567
|
+
#if HTTP1_UNALIGNED_MEMORY_ACCESS_ENABLED && HTTP_HEADERS_LOWERCASE
|
568
|
+
*((uint64_t *)start) == *((uint64_t *)"transfer") &&
|
569
|
+
*((uint64_t *)(start + 8)) == *((uint64_t *)"-encodin")
|
570
|
+
#else
|
571
|
+
HEADER_NAME_IS_EQ((char *)start, "transfer-encoding", 17)
|
572
|
+
#endif
|
573
|
+
) {
|
574
|
+
/* handle the special `transfer-encoding: chunked` header */
|
575
|
+
return http1_consume_header_transfer_encoding(parser, start, end_name,
|
576
|
+
start_value, end);
|
577
|
+
}
|
578
|
+
/* perform callback */
|
579
|
+
if (http1_on_header(parser, (char *)start, (end_name - start),
|
580
|
+
(char *)start_value, end - start_value))
|
581
|
+
return -1;
|
582
|
+
return 0;
|
583
|
+
}
|
584
|
+
|
585
|
+
inline static int http1_consume_header_trailer(http1_parser_s *parser,
|
586
|
+
uint8_t *start,
|
587
|
+
uint8_t *end_name,
|
588
|
+
uint8_t *start_value,
|
589
|
+
uint8_t *end) {
|
590
|
+
if ((end_name - start) > 1 && start[0] == 'x') {
|
591
|
+
/* X- headers are allowed */
|
592
|
+
goto white_listed;
|
593
|
+
}
|
594
|
+
|
595
|
+
/* white listed trailer names */
|
596
|
+
const struct {
|
597
|
+
char *name;
|
598
|
+
long len;
|
599
|
+
} http1_trailer_white_list[] = {
|
600
|
+
{"server-timing", 13}, /* specific for client data... */
|
601
|
+
{NULL, 0}, /* end of list marker */
|
602
|
+
};
|
603
|
+
for (size_t i = 0; http1_trailer_white_list[i].name; ++i) {
|
604
|
+
if ((long)(end_name - start) == http1_trailer_white_list[i].len &&
|
605
|
+
HEADER_NAME_IS_EQ((char *)start, http1_trailer_white_list[i].name,
|
606
|
+
http1_trailer_white_list[i].len)) {
|
607
|
+
/* header disallowed here */
|
608
|
+
goto white_listed;
|
609
|
+
}
|
610
|
+
}
|
611
|
+
return 0;
|
612
|
+
white_listed:
|
613
|
+
/* perform callback */
|
614
|
+
if (http1_on_header(parser, (char *)start, (end_name - start),
|
615
|
+
(char *)start_value, end - start_value))
|
616
|
+
return -1;
|
617
|
+
return 0;
|
618
|
+
}
|
619
|
+
|
620
|
+
inline static int http1_consume_header(http1_parser_s *parser, uint8_t *start,
|
621
|
+
uint8_t *end) {
|
622
|
+
uint8_t *end_name = start;
|
623
|
+
/* divide header name from data */
|
624
|
+
if (!seek2ch(&end_name, end, ':'))
|
625
|
+
return -1;
|
626
|
+
if (end_name[-1] == ' ' || end_name[-1] == '\t')
|
627
|
+
return -1;
|
628
|
+
#if HTTP_HEADERS_LOWERCASE
|
629
|
+
for (uint8_t *t = start; t < end_name; t++) {
|
630
|
+
*t = http_tolower(*t);
|
631
|
+
}
|
632
|
+
#endif
|
633
|
+
uint8_t *start_value = end_name + 1;
|
634
|
+
// clear away leading white space from value.
|
635
|
+
while (start_value < end &&
|
636
|
+
(start_value[0] == ' ' || start_value[0] == '\t')) {
|
637
|
+
start_value++;
|
638
|
+
};
|
639
|
+
return (parser->state.read ? http1_consume_header_trailer
|
640
|
+
: http1_consume_header_top)(
|
641
|
+
parser, start, end_name, start_value, end);
|
642
|
+
}
|
643
|
+
|
644
|
+
/* *****************************************************************************
|
645
|
+
HTTP/1.1 Body handling
|
646
|
+
***************************************************************************** */
|
647
|
+
|
648
|
+
inline static int http1_consume_body_streamed(http1_parser_s *parser,
|
649
|
+
void *buffer, size_t length,
|
650
|
+
uint8_t **start) {
|
651
|
+
uint8_t *end = *start + parser->state.content_length - parser->state.read;
|
652
|
+
uint8_t *const stop = ((uint8_t *)buffer) + length;
|
653
|
+
if (end > stop)
|
654
|
+
end = stop;
|
655
|
+
if (end > *start &&
|
656
|
+
http1_on_body_chunk(parser, (char *)(*start), end - *start))
|
657
|
+
return -1;
|
658
|
+
parser->state.read += (end - *start);
|
659
|
+
*start = end;
|
660
|
+
if (parser->state.content_length <= parser->state.read)
|
661
|
+
parser->state.reserved |= HTTP1_P_FLAG_COMPLETE;
|
662
|
+
return 0;
|
663
|
+
}
|
664
|
+
|
665
|
+
inline static int http1_consume_body_chunked(http1_parser_s *parser,
|
666
|
+
void *buffer, size_t length,
|
667
|
+
uint8_t **start) {
|
668
|
+
uint8_t *const stop = ((uint8_t *)buffer) + length;
|
669
|
+
uint8_t *end = *start;
|
670
|
+
while (*start < stop) {
|
671
|
+
if (parser->state.content_length == 0) {
|
672
|
+
if (end + 2 >= stop)
|
673
|
+
return 0;
|
674
|
+
if ((end[0] == '\r' && end[1] == '\n')) {
|
675
|
+
/* remove tailing EOL that wasn't processed and retest */
|
676
|
+
end += 2;
|
677
|
+
*start = end;
|
678
|
+
if (end + 2 >= stop)
|
679
|
+
return 0;
|
680
|
+
}
|
681
|
+
long long chunk_len = http1_atol16(end, (const uint8_t **)&end);
|
682
|
+
if (end + 2 > stop) /* overflowed? */
|
683
|
+
return 0;
|
684
|
+
if ((end[0] != '\r' || end[1] != '\n'))
|
685
|
+
return -1; /* required EOL after content length */
|
686
|
+
end += 2;
|
687
|
+
|
688
|
+
parser->state.content_length = 0 - chunk_len;
|
689
|
+
*start = end;
|
690
|
+
if (parser->state.content_length == 0) {
|
691
|
+
/* all chunked data was parsed */
|
692
|
+
/* update content-length */
|
693
|
+
parser->state.content_length = parser->state.read;
|
694
|
+
#ifdef HTTP_ADD_CONTENT_LENGTH_HEADER_IF_MISSING
|
695
|
+
{ /* add virtual header ... ? */
|
696
|
+
char buf[512];
|
697
|
+
size_t buf_len = 512;
|
698
|
+
size_t tmp_len = parser->state.read;
|
699
|
+
buf[--buf_len] = 0;
|
700
|
+
while (tmp_len) {
|
701
|
+
size_t mod = tmp_len / 10;
|
702
|
+
buf[--buf_len] = '0' + (tmp_len - (mod * 10));
|
703
|
+
tmp_len = mod;
|
704
|
+
}
|
705
|
+
if (!(parser->state.reserved & HTTP1_P_FLAG_CLENGTH) &&
|
706
|
+
http1_on_header(parser, "content-length", 14,
|
707
|
+
(char *)buf + buf_len, 511 - buf_len)) {
|
708
|
+
return -1;
|
709
|
+
}
|
710
|
+
}
|
711
|
+
#endif
|
712
|
+
/* FIXME: consume trailing EOL */
|
713
|
+
if (*start + 2 <= stop && (start[0][0] == '\r' || start[0][0] == '\n'))
|
714
|
+
*start += 1 + (start[0][1] == '\r' || start[0][1] == '\n');
|
715
|
+
else {
|
716
|
+
/* remove the "headers complete" and "trailer" flags */
|
717
|
+
parser->state.reserved =
|
718
|
+
HTTP1_P_FLAG_STATUS_LINE | HTTP1_P_FLAG_CLENGTH;
|
719
|
+
return -2;
|
720
|
+
}
|
721
|
+
/* the parsing complete flag */
|
722
|
+
parser->state.reserved |= HTTP1_P_FLAG_COMPLETE;
|
723
|
+
return 0;
|
724
|
+
}
|
725
|
+
}
|
726
|
+
end = *start + (0 - parser->state.content_length);
|
727
|
+
if (end > stop)
|
728
|
+
end = stop;
|
729
|
+
if (end > *start &&
|
730
|
+
http1_on_body_chunk(parser, (char *)(*start), end - *start)) {
|
731
|
+
return -1;
|
732
|
+
}
|
733
|
+
parser->state.read += (end - *start);
|
734
|
+
parser->state.content_length += (end - *start);
|
735
|
+
*start = end;
|
736
|
+
}
|
737
|
+
return 0;
|
738
|
+
}
|
739
|
+
|
740
|
+
inline static int http1_consume_body(http1_parser_s *parser, void *buffer,
|
741
|
+
size_t length, uint8_t **start) {
|
742
|
+
if (parser->state.content_length > 0 &&
|
743
|
+
parser->state.content_length > parser->state.read) {
|
744
|
+
/* normal, streamed data */
|
745
|
+
return http1_consume_body_streamed(parser, buffer, length, start);
|
746
|
+
} else if (parser->state.content_length <= 0 &&
|
747
|
+
(parser->state.reserved & HTTP1_P_FLAG_CHUNKED)) {
|
748
|
+
/* chuncked encoding */
|
749
|
+
return http1_consume_body_chunked(parser, buffer, length, start);
|
750
|
+
} else {
|
751
|
+
/* nothing to do - parsing complete */
|
752
|
+
parser->state.reserved |= HTTP1_P_FLAG_COMPLETE;
|
753
|
+
}
|
754
|
+
return 0;
|
755
|
+
}
|
756
|
+
|
757
|
+
/* *****************************************************************************
|
758
|
+
HTTP/1.1 parsre function
|
759
|
+
***************************************************************************** */
|
760
|
+
#if DEBUG
|
761
|
+
#include <assert.h>
|
762
|
+
#define HTTP1_ASSERT assert
|
763
|
+
#else
|
764
|
+
#define HTTP1_ASSERT(...)
|
765
|
+
#endif
|
101
766
|
|
102
767
|
/**
|
103
768
|
* Returns the amount of data actually consumed by the parser.
|
@@ -114,17 +779,95 @@ struct http1_fio_parser_args_s {
|
|
114
779
|
* A request / response callback automatically stops the parsing process,
|
115
780
|
* allowing the user to adjust or refresh the state of the data.
|
116
781
|
*/
|
117
|
-
size_t
|
118
|
-
|
119
|
-
static inline __attribute__((unused)) size_t
|
120
|
-
http1_fio_parser(struct http1_fio_parser_args_s args) {
|
121
|
-
if (!args.length)
|
782
|
+
static size_t http1_parse(http1_parser_s *parser, void *buffer, size_t length) {
|
783
|
+
if (!length)
|
122
784
|
return 0;
|
123
|
-
|
785
|
+
HTTP1_ASSERT(parser && buffer);
|
786
|
+
parser->state.next = NULL;
|
787
|
+
uint8_t *start = (uint8_t *)buffer;
|
788
|
+
uint8_t *end = start;
|
789
|
+
uint8_t *const stop = start + length;
|
790
|
+
uint8_t eol_len = 0;
|
791
|
+
#define HTTP1_CONSUMED ((size_t)((uintptr_t)start - (uintptr_t)buffer))
|
792
|
+
|
793
|
+
re_eval:
|
794
|
+
switch ((parser->state.reserved & 7)) {
|
795
|
+
|
796
|
+
case 0: /* request / response line */
|
797
|
+
/* clear out any leading white space */
|
798
|
+
while ((start < stop) &&
|
799
|
+
(*start == '\r' || *start == '\n' || *start == ' ' || *start == 0)) {
|
800
|
+
++start;
|
801
|
+
}
|
802
|
+
end = start;
|
803
|
+
/* make sure the whole line is available*/
|
804
|
+
if (!(eol_len = seek2eol(&end, stop)))
|
805
|
+
return HTTP1_CONSUMED;
|
806
|
+
|
807
|
+
if (start[0] == 'H' && start[1] == 'T' && start[2] == 'T' &&
|
808
|
+
start[3] == 'P') {
|
809
|
+
/* HTTP response */
|
810
|
+
if (http1_consume_response_line(parser, start, end - eol_len + 1))
|
811
|
+
goto error;
|
812
|
+
} else if (http_tolower(start[0]) >= 'a' && http_tolower(start[0]) <= 'z') {
|
813
|
+
/* HTTP request */
|
814
|
+
if (http1_consume_request_line(parser, start, end - eol_len + 1))
|
815
|
+
goto error;
|
816
|
+
} else
|
817
|
+
goto error;
|
818
|
+
end = start = end + 1;
|
819
|
+
parser->state.reserved |= HTTP1_P_FLAG_STATUS_LINE;
|
820
|
+
|
821
|
+
/* fallthrough */
|
822
|
+
case 1: /* headers */
|
823
|
+
do {
|
824
|
+
if (start >= stop)
|
825
|
+
return HTTP1_CONSUMED; /* buffer ended on header line */
|
826
|
+
if (*start == '\r' || *start == '\n') {
|
827
|
+
goto finished_headers; /* empty line, end of headers */
|
828
|
+
}
|
829
|
+
end = start;
|
830
|
+
if (!(eol_len = seek2eol(&end, stop)))
|
831
|
+
return HTTP1_CONSUMED;
|
832
|
+
if (http1_consume_header(parser, start, end - eol_len + 1))
|
833
|
+
goto error;
|
834
|
+
end = start = end + 1;
|
835
|
+
} while ((parser->state.reserved & HTTP1_P_FLAG_HEADER_COMPLETE) == 0);
|
836
|
+
finished_headers:
|
837
|
+
++start;
|
838
|
+
if (*start == '\n')
|
839
|
+
++start;
|
840
|
+
end = start;
|
841
|
+
parser->state.reserved |= HTTP1_P_FLAG_HEADER_COMPLETE;
|
842
|
+
/* fallthrough */
|
843
|
+
case (HTTP1_P_FLAG_HEADER_COMPLETE | HTTP1_P_FLAG_STATUS_LINE):
|
844
|
+
/* request body */
|
845
|
+
{
|
846
|
+
int t3 = http1_consume_body(parser, buffer, length, &start);
|
847
|
+
switch (t3) {
|
848
|
+
case -1:
|
849
|
+
goto error;
|
850
|
+
case -2:
|
851
|
+
goto re_eval;
|
852
|
+
}
|
853
|
+
break;
|
854
|
+
}
|
855
|
+
}
|
856
|
+
/* are we done ? */
|
857
|
+
if (parser->state.reserved & HTTP1_P_FLAG_COMPLETE) {
|
858
|
+
parser->state.next = start;
|
859
|
+
if (((parser->state.reserved & HTTP1_P_FLAG_RESPONSE)
|
860
|
+
? http1_on_response
|
861
|
+
: http1_on_request)(parser))
|
862
|
+
goto error;
|
863
|
+
parser->state = (struct http1_parser_protected_read_only_state_s){0};
|
864
|
+
}
|
865
|
+
return HTTP1_CONSUMED;
|
866
|
+
error:
|
867
|
+
http1_on_error(parser);
|
868
|
+
parser->state = (struct http1_parser_protected_read_only_state_s){0};
|
869
|
+
return length;
|
870
|
+
#undef HTTP1_CONSUMED
|
124
871
|
}
|
125
|
-
#if __STDC_VERSION__ >= 199901L
|
126
|
-
#define http1_fio_parser(...) \
|
127
|
-
http1_fio_parser((struct http1_fio_parser_args_s){__VA_ARGS__})
|
128
|
-
#endif
|
129
872
|
|
130
873
|
#endif
|