Skip to content

Commit 4d8467f

Browse files
Merge branch 'master' into master
2 parents a6c7aff + aa6df29 commit 4d8467f

7 files changed

Lines changed: 87 additions & 15 deletions

File tree

.github/workflows/build_and_test.yml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,8 @@ jobs:
137137
shell: bash
138138

139139
- name: Save report
140-
uses: actions/upload-artifact@v6
141-
if: matrix.os == 'ubuntu-latest' && matrix.compiler == 'gcc' && matrix.cxx_stdlib == 'libstdc++' && matrix.asio_type == 'boost' && !startsWith(github.ref, 'refs/heads/v')
140+
uses: actions/upload-artifact@v7
141+
if: matrix.os == 'ubuntu-latest' && matrix.compiler == 'gcc' && matrix.cxx_stdlib == 'libstdc++' && matrix.asio_type == 'boost' && !startsWith(github.ref, 'refs/tags/v')
142142
with:
143143
name: coveralls.json
144144
path: coveralls.json
@@ -158,9 +158,8 @@ jobs:
158158
ls
159159
shell: bash
160160

161-
- name: Upload Linux packages
162-
uses: actions/upload-artifact@v6
163-
if: matrix.os == 'ubuntu-latest' && matrix.compiler == 'gcc' && matrix.cxx_stdlib == 'libstdc++' && matrix.asio_type == 'boost' && startsWith(github.ref, 'refs/heads/v')
161+
- uses: actions/upload-artifact@v7
162+
if: matrix.os == 'ubuntu-latest' && matrix.compiler == 'gcc' && matrix.cxx_stdlib == 'libstdc++' && matrix.asio_type == 'boost' && startsWith(github.ref, 'refs/tags/v')
164163
with:
165164
name: packages
166165
overwrite: true

.github/workflows/cifuzz.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
fuzz-seconds: 800
2626
output-sarif: true
2727
- name: Upload Crash
28-
uses: actions/upload-artifact@v6
28+
uses: actions/upload-artifact@v7
2929
if: failure() && steps.build.outcome == 'success'
3030
with:
3131
name: artifacts

.github/workflows/submit_coverage.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
if: github.event.workflow_run.conclusion == 'success'
1717
steps:
1818
- name: Download artifact
19-
uses: dawidd6/action-download-artifact@v14
19+
uses: dawidd6/action-download-artifact@v16
2020
with:
2121
workflow: ${{ github.event.workflow_run.workflow_id }}
2222
workflow_conclusion: success

CMakeLists.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,9 @@ if(CROW_USE_BOOST)
114114
if(Boost_VERSION VERSION_LESS 1.89)
115115
find_package(Boost 1.64 COMPONENTS system REQUIRED)
116116
else()
117-
add_library(Boost::system ALIAS Boost::headers)
117+
if(NOT TARGET Boost::system)
118+
add_library(Boost::system ALIAS Boost::headers)
119+
endif()
118120
endif()
119121
target_link_libraries(Crow
120122
INTERFACE

include/crow/json.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1740,6 +1740,26 @@ namespace crow // NOTE: Already documented in "crow/app.h"
17401740
return const_cast<wvalue*>(this)->operator[](index);
17411741
}
17421742

1743+
/// Check if the object contains the given key.
1744+
bool has(const char* key) const
1745+
{
1746+
return has(std::string(key));
1747+
}
1748+
1749+
/// Check if the object contains the given key.
1750+
bool has(const std::string& key) const
1751+
{
1752+
if (t_ != type::Object)
1753+
return false;
1754+
if (!o)
1755+
return false;
1756+
#if (__cplusplus>=202002L)
1757+
return o->contains(key);
1758+
#else
1759+
return o->count(key)>0;
1760+
#endif
1761+
}
1762+
17431763
int count(const std::string& str) const
17441764
{
17451765
if (t_ != type::Object)
@@ -2042,6 +2062,12 @@ namespace crow // NOTE: Already documented in "crow/app.h"
20422062

20432063
return dump(DontIndent);
20442064
}
2065+
2066+
/// Return json string.
2067+
explicit operator std::string() const
2068+
{
2069+
return dump();
2070+
}
20452071
};
20462072

20472073
// Used for accessing the internals of a wvalue

include/crow/query_string.h

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,33 @@ inline int qs_strncmp(const char * s, const char * qs, size_t n)
6464
if ( u1 == '+' ) { u1 = ' '; }
6565
if ( u1 == '%' ) // easier/safer than scanf
6666
{
67-
unyb = static_cast<unsigned char>(*s++);
68-
lnyb = static_cast<unsigned char>(*s++);
69-
if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) )
67+
// Check that next two chars exist and are valid hex before reading
68+
if ( CROW_QS_ISHEX(s[0]) && CROW_QS_ISHEX(s[1]) )
69+
{
70+
unyb = static_cast<unsigned char>(*s++);
71+
lnyb = static_cast<unsigned char>(*s++);
7072
u1 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
73+
}
7174
else
75+
{
7276
u1 = '\0';
77+
}
7378
}
7479

7580
if ( u2 == '+' ) { u2 = ' '; }
7681
if ( u2 == '%' ) // easier/safer than scanf
7782
{
78-
unyb = static_cast<unsigned char>(*qs++);
79-
lnyb = static_cast<unsigned char>(*qs++);
80-
if ( CROW_QS_ISHEX(unyb) && CROW_QS_ISHEX(lnyb) )
83+
// Check that next two chars exist and are valid hex before reading
84+
if ( CROW_QS_ISHEX(qs[0]) && CROW_QS_ISHEX(qs[1]) )
85+
{
86+
unyb = static_cast<unsigned char>(*qs++);
87+
lnyb = static_cast<unsigned char>(*qs++);
8188
u2 = (CROW_QS_HEX2DEC(unyb) * 16) + CROW_QS_HEX2DEC(lnyb);
89+
}
8290
else
91+
{
8392
u2 = '\0';
93+
}
8494
}
8595

8696
if ( u1 != u2 )
@@ -150,7 +160,9 @@ inline int qs_decode(char * qs)
150160
if ( qs[j] == '+' ) { qs[i] = ' '; }
151161
else if ( qs[j] == '%' ) // easier/safer than scanf
152162
{
153-
if ( ! CROW_QS_ISHEX(qs[j+1]) || ! CROW_QS_ISHEX(qs[j+2]) )
163+
// Check bounds before reading: ensure j+1 and j+2 are within string
164+
if ( qs[j+1] == '\0' || qs[j+2] == '\0' ||
165+
! CROW_QS_ISHEX(qs[j+1]) || ! CROW_QS_ISHEX(qs[j+2]) )
154166
{
155167
qs[i] = '\0';
156168
return i;

tests/query_string_tests.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,37 @@ TEST_CASE( "query string keys" )
4242
return key == entry.first;});
4343
REQUIRE(exist == true);
4444
}
45+
}
46+
47+
TEST_CASE( "malformed percent encoding - trailing percent" )
48+
{
49+
// URL ending with '%' should not cause heap overflow (Issue #1126)
50+
crow::query_string qs1("https://example.com/asdf?%");
51+
auto keys1 = qs1.keys();
52+
// Should handle gracefully without crashing
53+
if (!keys1.empty()) {
54+
(void)qs1.get(keys1[0]);
55+
}
56+
57+
// URL with single hex digit after %
58+
crow::query_string qs2("https://example.com/?key=%2");
59+
auto keys2 = qs2.keys();
60+
if (!keys2.empty()) {
61+
(void)qs2.get(keys2[0]);
62+
}
63+
64+
// URL with % followed by non-hex characters
65+
crow::query_string qs3("https://example.com/?key=%GG");
66+
auto keys3 = qs3.keys();
67+
if (!keys3.empty()) {
68+
(void)qs3.get(keys3[0]);
69+
}
70+
71+
// Valid percent encoding should still work
72+
crow::query_string qs4("https://example.com/?key=%20value");
73+
auto val = qs4.get("key");
74+
REQUIRE(val != nullptr);
75+
REQUIRE(std::string(val) == " value");
76+
77+
REQUIRE(true); // Test passes if no crash/sanitizer error
4578
}

0 commit comments

Comments
 (0)