summaryrefslogtreecommitdiff
path: root/src/chunked.cc
blob: 99aea0d0a4a470f1695aa7f37507f3ffe52fc8ba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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
101
102
103
104
105
106
// -*- mode: c++; c-basic-offset: 2; -*-

#include "common.hh"

#include <cerrno>
#include <cstdint>
#include <cstdlib>

#include "chunked.hh"

namespace {

enum State {
  CHUNK,
  IN_CHUNK,
  TRAILER,
  DONE,
};

class ChunkedImpl : public Chunked {
public:
  ChunkedImpl()
    : state_(CHUNK), good_(true) {
  }

  size_t add(void const* data, size_t avail) override {
    if (!good_) return 0;
    auto const start = reinterpret_cast<char const*>(data);
    auto const end = start + avail;
    auto d = start;
    while (true) {
      if (d == end) return avail;
      switch (state_) {
      case CHUNK: {
        auto p = find_crlf(d, end);
        if (!p) return d - start;
        char* x = nullptr;
        errno = 0;
        auto tmp = strtoull(d, &x, 16);
        if (errno || (x != p && (!x || *x != ';'))) {
          good_ = false;
          return d - start;
        }
        size_ = tmp;
        d = p + 2;
        if (size_ == 0) {
          // Last chunk
          state_ = TRAILER;
        } else {
          state_ = IN_CHUNK;
        }
        break;
      }
      case IN_CHUNK:
        if (static_cast<uint64_t>(end - d) < size_) {
          return avail;
        }
        d += size_;
        state_ = CHUNK;
        break;
      case TRAILER: {
        auto p = find_crlf(d, end);
        if (!p) return d - start;
        if (p == d) {
          state_ = DONE;
        }
        d = p + 2;
        break;
      }
      case DONE:
        return d - start;
      }
    }
  }

  bool good() const override {
    return good_;
  }

  bool eof() const override {
    return state_ == DONE;
  }

private:
  char const* find_crlf(char const* start, char const* end) {
    for (; start != end; ++start) {
      if (*start == '\r') {
        if (start + 1 == end) break;
        if (start[1] == '\n') return start;
      }
    }
    return nullptr;
  }

  State state_;
  bool good_;
  uint64_t size_;
};

}  // namespace

// static
Chunked* Chunked::create() {
  return new ChunkedImpl();
}