1
#pragma once
2

            
3
#include <chrono>
4
#include <functional>
5
#include <memory>
6

            
7
#include "envoy/extensions/http/stateful_session/cookie/v3/cookie.pb.h"
8
#include "envoy/http/hash_policy.h"
9
#include "envoy/http/stateful_session.h"
10

            
11
#include "source/common/common/base64.h"
12
#include "source/common/http/utility.h"
13
#include "source/extensions/http/stateful_session/cookie/cookie.pb.h"
14

            
15
namespace Envoy {
16
namespace Extensions {
17
namespace Http {
18
namespace StatefulSession {
19
namespace Cookie {
20

            
21
using CookieBasedSessionStateProto =
22
    envoy::extensions::http::stateful_session::cookie::v3::CookieBasedSessionState;
23

            
24
class CookieBasedSessionStateFactory : public Envoy::Http::SessionStateFactory {
25
public:
26
  class SessionStateImpl : public Envoy::Http::SessionState {
27
  public:
28
    SessionStateImpl(absl::optional<std::string> address,
29
                     const CookieBasedSessionStateFactory& factory, TimeSource& time_source)
30
19
        : upstream_address_(std::move(address)), factory_(factory), time_source_(time_source) {}
31

            
32
17
    absl::optional<absl::string_view> upstreamAddress() const override { return upstream_address_; }
33
    bool onUpdate(absl::string_view host_address, Envoy::Http::ResponseHeaderMap& headers) override;
34

            
35
  private:
36
    absl::optional<std::string> upstream_address_;
37
    const CookieBasedSessionStateFactory& factory_;
38
    TimeSource& time_source_;
39
  };
40

            
41
  CookieBasedSessionStateFactory(const CookieBasedSessionStateProto& config,
42
                                 TimeSource& time_source);
43

            
44
24
  Envoy::Http::SessionStatePtr create(Envoy::Http::RequestHeaderMap& headers) const override {
45
24
    if (!requestPathMatch(headers.getPathValue())) {
46
5
      return nullptr;
47
5
    }
48

            
49
19
    return std::make_unique<SessionStateImpl>(parseAddress(headers), *this, time_source_);
50
24
  }
51

            
52
54
  bool requestPathMatch(absl::string_view request_path) const {
53
54
    ASSERT(path_matcher_ != nullptr);
54
54
    return path_matcher_(request_path);
55
54
  }
56

            
57
private:
58
19
  absl::optional<std::string> parseAddress(const Envoy::Http::RequestHeaderMap& headers) const {
59
19
    const std::string cookie_value = Envoy::Http::Utility::parseCookieValue(headers, name_);
60
19
    if (cookie_value.empty()) {
61
6
      return absl::nullopt;
62
6
    }
63
13
    const std::string decoded_value = Envoy::Base64::decode(cookie_value);
64
13
    std::string address;
65

            
66
    // Try to interpret the cookie as proto payload.
67
    // Otherwise treat it as "old" style format, which is ip-address:port.
68
13
    envoy::Cookie cookie;
69
13
    if (cookie.ParseFromString(decoded_value)) {
70
11
      address = cookie.address();
71
11
      if (address.empty()) {
72
        return absl::nullopt;
73
      }
74

            
75
11
      if (cookie.expires() != 0) {
76
10
        const std::chrono::seconds expiry_time(cookie.expires());
77
10
        const auto now = std::chrono::duration_cast<std::chrono::seconds>(
78
10
            (time_source_.monotonicTime()).time_since_epoch());
79
10
        if (now > expiry_time) {
80
          // Ignore the address extracted from the cookie. This will cause
81
          // upstream cluster to select a new host and new cookie will be generated.
82
2
          return absl::nullopt;
83
2
        }
84
10
      }
85
11
    } else {
86
2
      ENVOY_LOG_ONCE_MISC(
87
2
          warn, "Non-proto cookie format detected. This format will be rejected in the future.");
88
2
      address = decoded_value;
89
2
    }
90

            
91
11
    return address.empty() ? absl::nullopt : absl::make_optional(std::move(address));
92
13
  }
93

            
94
10
  std::string makeSetCookie(const std::string& address) const {
95
10
    return Envoy::Http::Utility::makeSetCookieValue(name_, address, path_, ttl_, true, attributes_);
96
10
  }
97

            
98
  const std::string name_;
99
  const std::chrono::seconds ttl_;
100
  const std::string path_;
101
  std::vector<Envoy::Http::CookieAttribute> attributes_;
102
  TimeSource& time_source_;
103

            
104
  std::function<bool(absl::string_view)> path_matcher_;
105
};
106

            
107
} // namespace Cookie
108
} // namespace StatefulSession
109
} // namespace Http
110
} // namespace Extensions
111
} // namespace Envoy