@@ -670,6 +670,7 @@ struct Request {
670670 std::function<bool ()> is_connection_closed = []() { return true ; };
671671
672672 // for client
673+ std::vector<std::string> accept_content_types;
673674 ResponseHandler response_handler;
674675 ContentReceiverWithProgress content_receiver;
675676 Progress progress;
@@ -2491,6 +2492,9 @@ bool parse_multipart_boundary(const std::string &content_type,
24912492
24922493bool parse_range_header (const std::string &s, Ranges &ranges);
24932494
2495+ bool parse_accept_header (const std::string &s,
2496+ std::vector<std::string> &content_types);
2497+
24942498int close_socket (socket_t sock);
24952499
24962500ssize_t send_socket (socket_t sock, const void *ptr, size_t size, int flags);
@@ -5026,6 +5030,123 @@ inline bool parse_range_header(const std::string &s, Ranges &ranges) try {
50265030} catch (...) { return false ; }
50275031#endif
50285032
5033+ inline bool parse_accept_header (const std::string &s,
5034+ std::vector<std::string> &content_types) {
5035+ content_types.clear ();
5036+
5037+ // Empty string is considered valid (no preference)
5038+ if (s.empty ()) { return true ; }
5039+
5040+ // Check for invalid patterns: leading/trailing commas or consecutive commas
5041+ if (s.front () == ' ,' || s.back () == ' ,' ||
5042+ s.find (" ,," ) != std::string::npos) {
5043+ return false ;
5044+ }
5045+
5046+ struct AcceptEntry {
5047+ std::string media_type;
5048+ double quality;
5049+ int order; // Original order in header
5050+ };
5051+
5052+ std::vector<AcceptEntry> entries;
5053+ int order = 0 ;
5054+ bool has_invalid_entry = false ;
5055+
5056+ // Split by comma and parse each entry
5057+ split (s.data (), s.data () + s.size (), ' ,' , [&](const char *b, const char *e) {
5058+ std::string entry (b, e);
5059+ entry = trim_copy (entry);
5060+
5061+ if (entry.empty ()) {
5062+ has_invalid_entry = true ;
5063+ return ;
5064+ }
5065+
5066+ AcceptEntry accept_entry;
5067+ accept_entry.quality = 1.0 ; // Default quality
5068+ accept_entry.order = order++;
5069+
5070+ // Find q= parameter
5071+ auto q_pos = entry.find (" ;q=" );
5072+ if (q_pos == std::string::npos) { q_pos = entry.find (" ; q=" ); }
5073+
5074+ if (q_pos != std::string::npos) {
5075+ // Extract media type (before q parameter)
5076+ accept_entry.media_type = trim_copy (entry.substr (0 , q_pos));
5077+
5078+ // Extract quality value
5079+ auto q_start = entry.find (' =' , q_pos) + 1 ;
5080+ auto q_end = entry.find (' ;' , q_start);
5081+ if (q_end == std::string::npos) { q_end = entry.length (); }
5082+
5083+ std::string quality_str =
5084+ trim_copy (entry.substr (q_start, q_end - q_start));
5085+ if (quality_str.empty ()) {
5086+ has_invalid_entry = true ;
5087+ return ;
5088+ }
5089+
5090+ try {
5091+ accept_entry.quality = std::stod (quality_str);
5092+ // Check if quality is in valid range [0.0, 1.0]
5093+ if (accept_entry.quality < 0.0 || accept_entry.quality > 1.0 ) {
5094+ has_invalid_entry = true ;
5095+ return ;
5096+ }
5097+ } catch (...) {
5098+ has_invalid_entry = true ;
5099+ return ;
5100+ }
5101+ } else {
5102+ // No quality parameter, use entire entry as media type
5103+ accept_entry.media_type = entry;
5104+ }
5105+
5106+ // Remove additional parameters from media type
5107+ auto param_pos = accept_entry.media_type .find (' ;' );
5108+ if (param_pos != std::string::npos) {
5109+ accept_entry.media_type =
5110+ trim_copy (accept_entry.media_type .substr (0 , param_pos));
5111+ }
5112+
5113+ // Basic validation of media type format
5114+ if (accept_entry.media_type .empty ()) {
5115+ has_invalid_entry = true ;
5116+ return ;
5117+ }
5118+
5119+ // Check for basic media type format (should contain '/' or be '*')
5120+ if (accept_entry.media_type != " *" &&
5121+ accept_entry.media_type .find (' /' ) == std::string::npos) {
5122+ has_invalid_entry = true ;
5123+ return ;
5124+ }
5125+
5126+ entries.push_back (accept_entry);
5127+ });
5128+
5129+ // Return false if any invalid entry was found
5130+ if (has_invalid_entry) { return false ; }
5131+
5132+ // Sort by quality (descending), then by original order (ascending)
5133+ std::sort (entries.begin (), entries.end (),
5134+ [](const AcceptEntry &a, const AcceptEntry &b) {
5135+ if (a.quality != b.quality ) {
5136+ return a.quality > b.quality ; // Higher quality first
5137+ }
5138+ return a.order < b.order ; // Earlier order first for same quality
5139+ });
5140+
5141+ // Extract sorted media types
5142+ content_types.reserve (entries.size ());
5143+ for (const auto &entry : entries) {
5144+ content_types.push_back (entry.media_type );
5145+ }
5146+
5147+ return true ;
5148+ }
5149+
50295150class MultipartFormDataParser {
50305151public:
50315152 MultipartFormDataParser () = default ;
@@ -7446,6 +7567,14 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
74467567 req.set_header (" LOCAL_ADDR" , req.local_addr );
74477568 req.set_header (" LOCAL_PORT" , std::to_string (req.local_port ));
74487569
7570+ if (req.has_header (" Accept" )) {
7571+ const auto &accept_header = req.get_header_value (" Accept" );
7572+ if (!detail::parse_accept_header (accept_header, req.accept_content_types )) {
7573+ res.status = StatusCode::BadRequest_400;
7574+ return write_response (strm, close_connection, req, res);
7575+ }
7576+ }
7577+
74497578 if (req.has_header (" Range" )) {
74507579 const auto &range_header_value = req.get_header_value (" Range" );
74517580 if (!detail::parse_range_header (range_header_value, req.ranges )) {
0 commit comments