1
#include "source/extensions/http/stateful_session/cookie/cookie.h"
2

            
3
#include "source/common/http/headers.h"
4
#include "source/common/runtime/runtime_features.h"
5

            
6
namespace Envoy {
7
namespace Extensions {
8
namespace Http {
9
namespace StatefulSession {
10
namespace Cookie {
11

            
12
bool CookieBasedSessionStateFactory::SessionStateImpl::onUpdate(
13
16
    absl::string_view host_address, Envoy::Http::ResponseHeaderMap& headers) {
14
16
  const bool host_changed =
15
16
      !upstream_address_.has_value() || host_address != upstream_address_.value();
16
16
  if (host_changed) {
17
    // Build proto message
18
10
    envoy::Cookie cookie;
19
10
    cookie.set_address(std::string(host_address));
20
10
    if (factory_.ttl_ != std::chrono::seconds::zero()) {
21
7
      const auto expiry_time = std::chrono::duration_cast<std::chrono::seconds>(
22
7
          (time_source_.monotonicTime() + std::chrono::seconds(factory_.ttl_)).time_since_epoch());
23
7
      cookie.set_expires(expiry_time.count());
24
7
    }
25
10
    std::string proto_string;
26
10
    cookie.SerializeToString(&proto_string);
27

            
28
10
    const std::string encoded_address =
29
10
        Envoy::Base64::encode(proto_string.data(), proto_string.length());
30
10
    headers.addReferenceKey(Envoy::Http::Headers::get().SetCookie,
31
10
                            factory_.makeSetCookie(encoded_address));
32
10
  }
33
16
  return host_changed;
34
16
}
35

            
36
CookieBasedSessionStateFactory::CookieBasedSessionStateFactory(
37
    const CookieBasedSessionStateProto& config, TimeSource& time_source)
38
22
    : name_(config.cookie().name()), ttl_(config.cookie().ttl().seconds()),
39
22
      path_(config.cookie().path()), time_source_(time_source) {
40
22
  if (name_.empty()) {
41
1
    throw EnvoyException("Cookie key cannot be empty for cookie based stateful sessions");
42
1
  }
43

            
44
  // Extract attributes from proto config
45
21
  for (const auto& proto_attr : config.cookie().attributes()) {
46
3
    attributes_.push_back({proto_attr.name(), proto_attr.value()});
47
3
  }
48

            
49
  // If no cookie path is specified or root cookie path is specified then this session state will
50
  // be enabled for any request.
51
21
  if (path_.empty() || path_ == "/") {
52
17
    path_matcher_ = [](absl::string_view) { return true; };
53
6
    return;
54
6
  }
55

            
56
  // If specified cookie path is ends with '/' then this session state will be enabled for the
57
  // requests with a path that starts with the cookie path.
58
  // For example the cookie path '/foo/' will matches request paths '/foo/bar' or '/foo/dir', but
59
  // will not match request path '/foo'.
60
15
  if (absl::EndsWith(path_, "/")) {
61
8
    path_matcher_ = [path = path_](absl::string_view request_path) {
62
8
      return absl::StartsWith(request_path, path);
63
8
    };
64
1
    return;
65
1
  }
66

            
67
30
  path_matcher_ = [path = path_](absl::string_view request_path) {
68
29
    if (absl::StartsWith(request_path, path)) {
69
      // Request path is same with cookie path.
70
22
      if (request_path.size() == path.size()) {
71
17
        return true;
72
17
      }
73

            
74
      // The next character of the matching part should be the slash ('/'), question mark ('?') or
75
      // number sign ('#').
76
5
      ASSERT(request_path.size() > path.size());
77
5
      const char next_char = request_path[path.size()];
78
5
      if (next_char == '/' || next_char == '?' || next_char == '#') {
79
4
        return true;
80
4
      }
81
5
    }
82
8
    return false;
83
29
  };
84
14
}
85

            
86
} // namespace Cookie
87
} // namespace StatefulSession
88
} // namespace Http
89
} // namespace Extensions
90
} // namespace Envoy