mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-07-01 02:41:48 +00:00
Add HttpMultipartScanner (#1197)
This is a non-blocking version of FindMultipartBoundary.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
105
wpiutil/src/test/native/cpp/HttpUtilTest.cpp
Normal file
105
wpiutil/src/test/native/cpp/HttpUtilTest.cpp
Normal 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
|
||||
Reference in New Issue
Block a user