Add HttpMultipartScanner (#1197)

This is a non-blocking version of FindMultipartBoundary.
This commit is contained in:
Peter Johnson
2018-08-20 22:00:54 -07:00
committed by GitHub
parent 9e37ee13de
commit 4a3e43d4a7
3 changed files with 222 additions and 0 deletions

View File

@@ -333,4 +333,78 @@ bool HttpConnection::Handshake(const HttpRequest& request,
return true;
}
void HttpMultipartScanner::SetBoundary(StringRef boundary) {
m_boundaryWith = "\n--";
m_boundaryWith += boundary;
m_boundaryWithout = "\n";
m_boundaryWithout += boundary;
m_dashes = kUnknown;
}
void HttpMultipartScanner::Reset(bool saveSkipped) {
m_saveSkipped = saveSkipped;
m_state = kBoundary;
m_posWith = 0;
m_posWithout = 0;
m_buf.resize(0);
}
StringRef HttpMultipartScanner::Execute(StringRef in) {
if (m_state == kDone) Reset(m_saveSkipped);
if (m_saveSkipped) m_buf += in;
size_t pos = 0;
if (m_state == kBoundary) {
for (char ch : in) {
++pos;
if (m_dashes != kWithout) {
if (ch == m_boundaryWith[m_posWith]) {
++m_posWith;
if (m_posWith == m_boundaryWith.size()) {
// Found the boundary; transition to padding
m_state = kPadding;
m_dashes = kWith; // no longer accept plain 'boundary'
break;
}
} else if (ch == m_boundaryWith[0]) {
m_posWith = 1;
} else {
m_posWith = 0;
}
}
if (m_dashes != kWith) {
if (ch == m_boundaryWithout[m_posWithout]) {
++m_posWithout;
if (m_posWithout == m_boundaryWithout.size()) {
// Found the boundary; transition to padding
m_state = kPadding;
m_dashes = kWithout; // no longer accept '--boundary'
break;
}
} else if (ch == m_boundaryWithout[0]) {
m_posWithout = 1;
} else {
m_posWithout = 0;
}
}
}
}
if (m_state == kPadding) {
for (char ch : in.drop_front(pos)) {
++pos;
if (ch == '\n') {
// Found the LF; return remaining input buffer (following it)
m_state = kDone;
if (m_saveSkipped) m_buf.resize(m_buf.size() - in.size() + pos);
return in.drop_front(pos);
}
}
}
// We consumed the entire input
return StringRef{};
}
} // namespace wpi

View File

@@ -141,6 +141,49 @@ class HttpConnection {
explicit operator bool() const { return stream && !is.has_error(); }
};
class HttpMultipartScanner {
public:
explicit HttpMultipartScanner(StringRef boundary, bool saveSkipped = false) {
Reset(saveSkipped);
SetBoundary(boundary);
}
// Change the boundary. This is only safe to do when IsDone() is true (or
// immediately after construction).
void SetBoundary(StringRef boundary);
// Reset the scanner. This allows reuse of internal buffers.
void Reset(bool saveSkipped = false);
// Execute the scanner. Will automatically call Reset() on entry if IsDone()
// is true.
// @param in input data
// @return the input not consumed; empty if all input consumed
StringRef Execute(StringRef in);
// Returns true when the boundary has been found.
bool IsDone() const { return m_state == kDone; }
// Get the skipped data. Will be empty if saveSkipped was false.
StringRef GetSkipped() const {
return m_saveSkipped ? StringRef{m_buf} : StringRef{};
}
private:
SmallString<64> m_boundaryWith, m_boundaryWithout;
// Internal state
enum State { kBoundary, kPadding, kDone };
State m_state;
size_t m_posWith, m_posWithout;
enum Dashes { kUnknown, kWith, kWithout };
Dashes m_dashes;
// Buffer
bool m_saveSkipped;
std::string m_buf;
};
} // namespace wpi
#include "HttpUtil.inl"

View File

@@ -0,0 +1,105 @@
/*----------------------------------------------------------------------------*/
/* Copyright (c) 2018 FIRST. All Rights Reserved. */
/* Open Source Software - may be modified and shared by FRC teams. The code */
/* must be accompanied by the FIRST BSD license file in the root directory of */
/* the project. */
/*----------------------------------------------------------------------------*/
#include "wpi/HttpUtil.h" // NOLINT(build/include_order)
#include "gtest/gtest.h"
namespace wpi {
TEST(HttpMultipartScannerTest, ExecuteExact) {
HttpMultipartScanner scanner("foo");
EXPECT_TRUE(scanner.Execute("abcdefg---\r\n--foo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
EXPECT_TRUE(scanner.GetSkipped().empty());
}
TEST(HttpMultipartScannerTest, ExecutePartial) {
HttpMultipartScanner scanner("foo");
EXPECT_TRUE(scanner.Execute("abcdefg--").empty());
EXPECT_FALSE(scanner.IsDone());
EXPECT_TRUE(scanner.Execute("-\r\n").empty());
EXPECT_FALSE(scanner.IsDone());
EXPECT_TRUE(scanner.Execute("--foo\r").empty());
EXPECT_FALSE(scanner.IsDone());
EXPECT_TRUE(scanner.Execute("\n").empty());
EXPECT_TRUE(scanner.IsDone());
}
TEST(HttpMultipartScannerTest, ExecuteTrailing) {
HttpMultipartScanner scanner("foo");
EXPECT_EQ(scanner.Execute("abcdefg---\r\n--foo\r\nxyz"), "xyz");
}
TEST(HttpMultipartScannerTest, ExecutePadding) {
HttpMultipartScanner scanner("foo");
EXPECT_EQ(scanner.Execute("abcdefg---\r\n--foo \r\nxyz"), "xyz");
EXPECT_TRUE(scanner.IsDone());
}
TEST(HttpMultipartScannerTest, SaveSkipped) {
HttpMultipartScanner scanner("foo", true);
scanner.Execute("abcdefg---\r\n--foo\r\n");
EXPECT_EQ(scanner.GetSkipped(), "abcdefg---\r\n--foo\r\n");
}
TEST(HttpMultipartScannerTest, Reset) {
HttpMultipartScanner scanner("foo", true);
scanner.Execute("abcdefg---\r\n--foo\r\n");
EXPECT_TRUE(scanner.IsDone());
EXPECT_EQ(scanner.GetSkipped(), "abcdefg---\r\n--foo\r\n");
scanner.Reset(true);
EXPECT_FALSE(scanner.IsDone());
scanner.SetBoundary("bar");
scanner.Execute("--foo\r\n--bar\r\n");
EXPECT_TRUE(scanner.IsDone());
EXPECT_EQ(scanner.GetSkipped(), "--foo\r\n--bar\r\n");
}
TEST(HttpMultipartScannerTest, WithoutDashes) {
HttpMultipartScanner scanner("foo", true);
EXPECT_TRUE(scanner.Execute("--\r\nfoo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
}
TEST(HttpMultipartScannerTest, SeqDashesDashes) {
HttpMultipartScanner scanner("foo", true);
EXPECT_TRUE(scanner.Execute("\r\n--foo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
EXPECT_TRUE(scanner.Execute("\r\n--foo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
}
TEST(HttpMultipartScannerTest, SeqDashesNoDashes) {
HttpMultipartScanner scanner("foo", true);
EXPECT_TRUE(scanner.Execute("\r\n--foo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
EXPECT_TRUE(scanner.Execute("\r\nfoo\r\n").empty());
EXPECT_FALSE(scanner.IsDone());
}
TEST(HttpMultipartScannerTest, SeqNoDashesDashes) {
HttpMultipartScanner scanner("foo", true);
EXPECT_TRUE(scanner.Execute("\r\nfoo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
EXPECT_TRUE(scanner.Execute("\r\n--foo\r\n").empty());
EXPECT_FALSE(scanner.IsDone());
}
TEST(HttpMultipartScannerTest, SeqNoDashesNoDashes) {
HttpMultipartScanner scanner("foo", true);
EXPECT_TRUE(scanner.Execute("\r\nfoo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
EXPECT_TRUE(scanner.Execute("\r\nfoo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
}
} // namespace wpi