proxygen
RFC1867Test.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015-present, Facebook, Inc.
3  * All rights reserved.
4  *
5  * This source code is licensed under the BSD-style license found in the
6  * LICENSE file in the root directory of this source tree. An additional grant
7  * of patent rights can be found in the PATENTS file in the same directory.
8  *
9  */
12 
15 
16 using namespace testing;
17 using std::unique_ptr;
18 using std::map;
19 using std::list;
20 using std::string;
21 using std::pair;
22 using folly::IOBuf;
23 using folly::IOBufQueue;
24 
25 namespace {
26 
27 const std::string kTestBoundary("abcdef");
28 
34 unique_ptr<IOBuf> makePost(
35  const map<string, string>& simpleFields,
36  const map<string, pair<string, string>>& explicitFiles,
37  const map<string, pair<string, size_t>>& randomFiles,
38  const string optExpHeaderSeqEnding = "") {
39  IOBufQueue result;
40  for (const auto& kv : simpleFields) {
41  result.append("--");
42  result.append(kTestBoundary);
43  result.append("\r\nContent-Disposition: form-data; name=\"");
44  result.append(kv.first);
45  result.append("\"\r\n\r\n");
46  result.append(kv.second);
47  result.append("\r\n");
48  }
49  for (const auto& kv: explicitFiles) {
50  result.append("--");
51  result.append(kTestBoundary);
52  result.append("\r\nContent-Disposition: form-data; name=\"");
53  result.append(kv.first + "\"");
54  auto& file = kv.second;
55  if (!file.first.empty()) {
56  result.append("; filename=\"" + file.first + "\"");
57  }
58  result.append(
59  "\r\n"
60  "Content-Type: text/plain\r\n"
61  "\r\n");
62  result.append(IOBuf::copyBuffer(file.second.data(), file.second.length()));
63  result.append("\r\n");
64  }
65  for (const auto& kv: randomFiles) {
66  result.append("--");
67  result.append(kTestBoundary);
68  result.append("\r\nContent-Disposition: form-data; name=\"");
69  result.append(kv.first + "\"");
70  auto& file = kv.second;
71  if (!file.first.empty()) {
72  result.append("; filename=\"" + file.first + "\"");
73  }
74  result.append(
75  "\r\n"
76  "Content-Type: text/plain\r\n"
77  "\r\n");
78  result.append(proxygen::makeBuf(file.second));
79  result.append("\r\n");
80  }
81  result.append("--");
82  result.append(kTestBoundary);
83  result.append(optExpHeaderSeqEnding);
84 
85  return result.move();
86 }
87 
88 } // namespace end
89 
90 namespace proxygen {
91 
93  public:
94  MOCK_METHOD4(onFieldStartImpl, int(const string& name, const std::string& filename,
95  std::shared_ptr<HTTPMessage> msg,
96  uint64_t bytesProcessed));
97  int onFieldStartImpl(const string& name, const std::string& filename,
98  std::unique_ptr<HTTPMessage> msg,
99  uint64_t bytesProcessed) {
100  std::shared_ptr<HTTPMessage> sh_msg(msg.release());
101  return onFieldStartImpl(name, filename, sh_msg, bytesProcessed);
102  }
103  int onFieldStart(const string& name, folly::Optional<std::string> filename,
104  std::unique_ptr<HTTPMessage> msg,
105  uint64_t bytesProcessed) override {
106  return onFieldStartImpl(name, filename.value_or(""), std::move(msg), bytesProcessed);
107  }
108  MOCK_METHOD2(onFieldData, int(std::shared_ptr<folly::IOBuf>, uint64_t));
109  int onFieldData(std::unique_ptr<folly::IOBuf> data,
110  uint64_t bytesProcessed) override {
111  std::shared_ptr<IOBuf> sh_data(data.release());
112  return onFieldData(sh_data, bytesProcessed);
113  }
114 
115  MOCK_METHOD2(onFieldEnd, void(bool, uint64_t));
116  MOCK_METHOD0(onError, void());
117 };
118 
119 class RFC1867Base {
120  public:
121 
122  void SetUp() {
123  codec_.setCallback(&callback_);
124  }
125 
126  void parse(unique_ptr<IOBuf> input, size_t chunkSize = 0) {
127  IOBufQueue ibuf{IOBufQueue::cacheChainLength()};
128  ibuf.append(std::move(input));
129  if (chunkSize == 0) {
130  chunkSize = ibuf.chainLength();
131  }
132  unique_ptr<IOBuf> rem;
133  while (!ibuf.empty()) {
134  auto chunk = ibuf.split(std::min(chunkSize, ibuf.chainLength()));
135  if (rem) {
136  rem->prependChain(std::move(chunk));
137  chunk = std::move(rem);
138  }
139  rem = codec_.onIngress(std::move(chunk));
140  }
141  codec_.onIngressEOM();
142  }
143 
144  protected:
145  void testSimple(unique_ptr<IOBuf> data,
146  size_t fileSize,
147  size_t splitSize,
148  size_t parts);
149 
151  RFC1867Codec codec_{kTestBoundary};
152 
153 };
154 
155 class RFC1867Test : public testing::Test, public RFC1867Base {
156  public:
157  void SetUp() override {
158  RFC1867Base::SetUp();
159  }
160 };
161 
167 void RFC1867Base::testSimple(unique_ptr<IOBuf> data,
168  size_t fileSize,
169  size_t splitSize,
170  size_t parts) {
171  size_t fileLength = 0;
172  IOBufQueue parsedData{IOBufQueue::cacheChainLength()};
173  EXPECT_CALL(callback_, onFieldStartImpl(_, _, _, _))
174  .Times(parts)
175  .WillRepeatedly(Return(0));
176  EXPECT_CALL(callback_, onFieldData(_, _))
177  .WillRepeatedly(Invoke([&] (std::shared_ptr<IOBuf> data, uint64_t) {
178  fileLength += data->computeChainDataLength();
179  parsedData.append(data->clone());
180  return 0;
181  }));
182  EXPECT_CALL(callback_, onFieldEnd(true, _))
183  .Times(parts)
184  .WillRepeatedly(Return());
185  parse(data->clone(), splitSize);
186  auto parsedDataBuf = parsedData.move();
187  if (fileLength > 0) {
188  // isChained() called from coalesce below asserts if no data has
189  // been added
190  parsedDataBuf->coalesce();
191  }
192  CHECK_EQ(fileLength, fileSize);
193 }
194 
195 TEST_F(RFC1867Test, TestSimplePost) {
196  size_t fileSize = 17;
197  auto data = makePost(
198  {{"foo", "bar"}, {"jojo", "binky"}}, {}, {{"file1", {"", fileSize}}});
199  testSimple(std::move(data), 3 + 5 + fileSize, 0, 3);
200 }
201 
202 TEST_F(RFC1867Test, TestSplits) {
203  for (size_t i = 1; i < 500; i++) {
204  size_t fileSize = 1000 + i;
205  auto data = makePost(
206  {{"foo", "bar"}, {"jojo", "binky"}}, {}, {{"file1", {"", fileSize}}});
207  testSimple(std::move(data), 3 + 5 + fileSize, i, 3);
208  }
209 }
210 
211 TEST_F(RFC1867Test, TestSplitsWithFilename) {
212  for (size_t i = 1; i < 500; i++) {
213  size_t fileSize = 1000 + i;
214  auto data = makePost({{"foo", "bar"}, {"jojo", "binky"}},
215  {},
216  {{"file1", {"file1.txt", fileSize}}});
217  testSimple(std::move(data), 3 + 5 + fileSize, i, 3);
218  }
219 }
220 
221 TEST_F(RFC1867Test, TestHeadersChunkExtraCr) {
222  // We are testing here that we correctly chunk when the parser has just
223  // finished parsing a CR.
224  auto numCRs = 5;
225  auto headerEndingSeq = "--" + string(numCRs, '\r') + "\n";
226  auto fileSize = 10;
227  auto data = makePost({{"foo", "bar"}, {"jojo", "binky"}},
228  {},
229  {{"file1", {"", fileSize}}},
230  headerEndingSeq);
231  // Math ensures we the parser will chunk at a '\r' with a numCRs-1
232  testSimple(std::move(data), 3 + 5 + fileSize, numCRs - 1, 3);
233 }
234 
235 class RFC1867CR : public testing::TestWithParam<string>, public RFC1867Base {
236  public:
237  void SetUp() override {
238  RFC1867Base::SetUp();
239  }
240 };
241 
242 
244  for (size_t i = 1; i < GetParam().size(); i++) {
245  auto data = makePost({{"foo", "bar"}, {"jojo", "binky"}},
246  {{"file1", {"dummy file name", GetParam()}}},
247  {});
248  testSimple(std::move(data), 3 + 5 + GetParam().size(), i, 3);
249  }
250 }
251 
253  ValueTest,
254  RFC1867CR,
255  ::testing::Values(
256  // embedded \r\n
257  string("zyx\r\nwvu", 8),
258  // leading \r
259  string("\rzyxwvut", 8),
260  // trailing \r
261  string("zyxwvut\r", 8),
262  // leading \n
263  string("\nzyxwvut", 8),
264  // trailing \n
265  string("zyxwvut\n", 8),
266  // all \r\n
267  string("\r\n\r\n\r\n\r\n", 8),
268  // all \r
269  string("\r\r\r\r\r\r\r\r", 8)
270  ));
271 
272 
273 
274 }
size_t parse(const char *buf, size_t len)
Definition: test.c:1591
INSTANTIATE_TEST_CASE_P(, CodeLocationForTESTP, Values(0))
#define MOCK_METHOD4(m,...)
std::unique_ptr< folly::IOBuf > makeBuf(uint32_t size)
Definition: TestUtils.cpp:36
void append(std::unique_ptr< folly::IOBuf > &&buf, bool pack=false)
Definition: IOBufQueue.cpp:143
TEST_F(TestInfoTest, Names)
constexpr detail::Map< Move > move
Definition: Base-inl.h:2567
void parse(unique_ptr< IOBuf > input, size_t chunkSize=0)
std::unique_ptr< folly::IOBuf > move()
Definition: IOBufQueue.h:459
void SetUp() override
std::unique_ptr< Codec > codec_
TEST_P(CodeLocationForTESTP, Verify)
int onFieldStart(const string &name, folly::Optional< std::string > filename, std::unique_ptr< HTTPMessage > msg, uint64_t bytesProcessed) override
StrictMock< Mock1867Callback > callback_
int onFieldData(std::unique_ptr< folly::IOBuf > data, uint64_t bytesProcessed) override
const char * name
Definition: http_parser.c:437
constexpr auto size(C const &c) -> decltype(c.size())
Definition: Access.h:45
LogLevel min
Definition: LogLevel.cpp:30
PolymorphicAction< internal::InvokeAction< FunctionImpl > > Invoke(FunctionImpl function_impl)
Encoder::MutableCompressedList list
static Map map(mapCap)
int onFieldStartImpl(const string &name, const std::string &filename, std::unique_ptr< HTTPMessage > msg, uint64_t bytesProcessed)
Definition: RFC1867Test.cpp:97
FOLLY_CPP14_CONSTEXPR Value value_or(U &&dflt) const &
Definition: Optional.h:330
void prependChain(std::unique_ptr< IOBuf > &&iobuf)
Definition: IOBuf.cpp:509
std::unique_ptr< IOBuf > copyBuffer(const folly::IOBuf &buf)
const char * string
Definition: Conv.cpp:212
#define EXPECT_CALL(obj, call)
const internal::AnythingMatcher _
folly::Function< void()> callback_
#define MOCK_METHOD2(m,...)
static constexpr uint64_t data[1]
Definition: Fingerprint.cpp:43
void SetUp() override
internal::ReturnAction< R > Return(R value)
#define MOCK_METHOD0(m,...)