Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

601

602

603

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

622

623

624

625

626

627

628

629

630

631

632

633

634

635

636

637

638

639

640

641

642

643

644

645

646

647

648

649

650

############################################################################### 

# 

# vim:ts=3:sw=3:expandtab 

# 

# Open source pyicap module retrofitted with tornado based socket read/write 

# 

# Copyright (C) 2017, Menlo Security, Inc. 

# 

# All rights reserved. 

# 

# Summary: Open source pyicap module (https://github.com/netom/pyicap) uses 

# multi-threading for handling icap transactions. This did not scale well. In 

# this implementation, we use tornado based socket io operations and leverage 

# pyicap for icap message parsing. The changes made here are minimal so that any 

# future changes/bugfixes to pyicap can be easily ported to this implementation 

############################################################################### 

import collections 

import random 

import socket 

import string 

import sys 

import time 

import urlparse 

 

import tornado.gen 

 

from ops_logging import get_logger 

 

logger = get_logger('pyicap') 

 

__version__ = "1.0" 

 

 

class NormalizedHeaderCache(dict): 

"""Dynamic cached mapping of header names to Http-Header-Case. 

 

Implemented as a dict subclass so that cache hits are as fast as a 

normal dict lookup, without the overhead of a python function 

call. 

 

This is a modified version of tornado.httputil's implementation to 

define default mappings. Defaults can be specified by assigning a 

lower case version of the header to the re-cased version. 

 

>>> normalized_headers = NormalizedHeaderCache(10) 

>>> normalized_headers["coNtent-TYPE"] 

'Content-Type' 

>>> normalized_headers["istag"] = 'ISTag' 

>>> normalized_headers["iStAg"] 

'ISTag' 

""" 

def __init__(self, size): 

super(NormalizedHeaderCache, self).__init__() 

self.size = size 

self.queue = collections.deque() 

 

def __missing__(self, key): 

key_l = key.lower() 

if key_l in self: 

normalized = self[key_l] 

else: 

normalized = "-".join([w.capitalize() for w in key.split("-")]) 

self[key] = normalized 

self.queue.append(key) 

if len(self.queue) > self.size: 

# Limit the size of the cache. LRU would be better, but this 

# simpler approach should be fine. In Python 2.7+ we could 

# use OrderedDict (or in 3.2+, @functools.lru_cache). 

old_key = self.queue.popleft() 

del self[old_key] 

return normalized 

 

 

_normalized_headers = NormalizedHeaderCache(1000) 

# Defined specific header to re-cased version 

_normalized_headers['istag'] = 'ISTag' 

 

 

class ICAPError(Exception): 

"""Signals a protocol error""" 

def __init__(self, code=500, message=None): 

if message is None: 

message = BaseICAPRequestHandler._responses[code] 

self.message = message 

super(ICAPError, self).__init__(message) 

self.code = code 

 

class BaseICAPRequestHandler(object): 

"""ICAP request handler base class. 

 

You have to subclass it and provide methods for each service 

endpoint. Every endpoint MUST have an _OPTION method, and either 

a _REQMOD or a _RESPMOD method. 

""" 

 

# The version of the ICAP protocol we support. 

protocol_version = "ICAP/1.0" 

 

# Table mapping response codes to messages; entries have the 

# form {code: (shortmessage, longmessage)}. 

# See RFC 2616 and RFC 3507 

_responses = { 

100: (b'Continue', b'Request received, please continue'), 

101: (b'Switching Protocols', 

b'Switching to new protocol; obey Upgrade header'), 

 

200: (b'OK', b'Request fulfilled, document follows'), 

201: (b'Created', b'Document created, URL follows'), 

202: (b'Accepted', 

b'Request accepted, processing continues off-line'), 

203: (b'Non-Authoritative Information', b'Request fulfilled from cache'), 

204: (b'No Content', b'Request fulfilled, nothing follows'), 

205: (b'Reset Content', b'Clear input form for further input.'), 

206: (b'Partial Content', b'Partial content follows.'), 

 

300: (b'Multiple Choices', 

b'Object has several resources -- see URI list'), 

301: (b'Moved Permanently', b'Object moved permanently -- see URI list'), 

302: (b'Found', b'Object moved temporarily -- see URI list'), 

303: (b'See Other', b'Object moved -- see Method and URL list'), 

304: (b'Not Modified', 

b'Document has not changed since given time'), 

305: (b'Use Proxy', 

b'You must use proxy specified in Location to access this ' 

b'resource.'), 

307: (b'Temporary Redirect', 

b'Object moved temporarily -- see URI list'), 

 

400: (b'Bad Request', 

b'Bad request syntax or unsupported method'), 

401: (b'Unauthorized', 

b'No permission -- see authorization schemes'), 

402: (b'Payment Required', 

b'No payment -- see charging schemes'), 

403: (b'Forbidden', 

b'Request forbidden -- authorization will not help'), 

404: (b'Not Found', b'Nothing matches the given URI'), 

405: (b'Method Not Allowed', 

b'Specified method is invalid for this resource.'), 

406: (b'Not Acceptable', b'URI not available in preferred format.'), 

407: (b'Proxy Authentication Required', b'You must authenticate with ' 

b'this proxy before proceeding.'), 

408: (b'Request Timeout', b'Request timed out; try again later.'), 

409: (b'Conflict', b'Request conflict.'), 

410: (b'Gone', 

b'URI no longer exists and has been permanently removed.'), 

411: (b'Length Required', b'Client must specify Content-Length.'), 

412: (b'Precondition Failed', b'Precondition in headers is false.'), 

413: (b'Request Entity Too Large', b'Entity is too large.'), 

414: (b'Request-URI Too Long', b'URI is too long.'), 

415: (b'Unsupported Media Type', b'Entity body in unsupported format.'), 

416: (b'Requested Range Not Satisfiable', 

b'Cannot satisfy request range.'), 

417: (b'Expectation Failed', 

b'Expect condition could not be satisfied.'), 

 

500: (b'Internal Server Error', b'Server got itself in trouble'), 

501: (b'Not Implemented', 

b'Server does not support this operation'), 

502: (b'Bad Gateway', b'Invalid responses from another server/proxy.'), 

503: (b'Service Unavailable', 

b'The server cannot process the request due to a high load'), 

504: (b'Gateway Timeout', 

b'The gateway server did not receive a timely response'), 

505: (b'Protocol Version Not Supported', b'Cannot fulfill request.'), 

 

} 

 

# The Python system version, truncated to its first component. 

_sys_version = "Python/" + sys.version.split()[0] 

 

# The server software version. You may want to override this. 

# The format is multiple whitespace-separated strings, 

# where each string is of the form name[/version]. 

_server_version = "BaseICAP/" + __version__ 

 

_weekdayname = [b'Mon', b'Tue', b'Wed', b'Thu', b'Fri', b'Sat', b'Sun'] 

 

_monthname = [None, b'Jan', b'Feb', b'Mar', b'Apr', b'May', b'Jun', b'Jul', 

b'Aug', b'Sep', b'Oct', b'Nov', b'Dec'] 

 

def __init__(self, icap_connection, close_connection=False): 

# Initialize handler state 

self.enc_req = None 

self.enc_req_headers = {} 

self.enc_res_status = None 

self.enc_res_headers = {} 

self.has_body = False 

self.servicename = None 

self.encapsulated = {} 

self.ieof = False 

self.eob = False 

self.preview = None 

self.allow = set() 

self.client_ip = None 

self.icap_headers = {} 

self.enc_headers = {} 

self.enc_status = None # Seriously, need better names 

self.enc_request = None 

self.icap_response_code = None 

self.icap_connection = icap_connection 

self.close_connection = close_connection 

self._icap_methods_map = { 

'OPTIONS': self.icap_options, 

'REQMOD': self.icap_reqmod, 

'RESPMOD': self.icap_respmod, 

} 

 

def _read_request(self, hdr_line): 

"""Read a HTTP or ICAP request line from input stream""" 

return hdr_line.strip().split(b' ', 2) 

 

def _read_headers(self, hdr_lines): 

"""Read a sequence of header lines""" 

headers = {} 

while True: 

line = hdr_lines.pop(0) 

if line == b'': 

break 

parts = line.split(':', 1) 

if len(parts) == 2: 

k = parts[0] 

v = parts[1] 

headers[k.lower()] = headers.get(k.lower(), []) + [v.strip()] 

return headers 

 

@tornado.gen.coroutine 

def read_chunk(self): 

"""Read a HTTP chunk 

 

Also handles the ieof chunk extension defined by the ICAP 

protocol by setting the ieof variable to True. It returns an 

empty line if the last chunk is read. Reading after the last 

chunks will return empty strings. 

""" 

 

# Don't try to read when there's no body 

if not self.has_body or self.eob: 

self.eob = True 

raise tornado.gen.Return(b'') 

 

line = yield self.icap_connection.read_line() 

if line == b'': 

# Connection was probably closed 

self.eob = True 

raise tornado.gen.Return(b'') 

 

line = line.strip() 

arr = line.split(b';', 1) 

chunk_size = 0 

try: 

chunk_size = int(arr[0], 16) 

except ValueError: 

raise ICAPError(400, 'Protocol error, could not read chunk') 

 

# Look for ieof chunk extension 

if len(arr) > 1 and arr[1].strip() == b'ieof': 

self.ieof = True 

 

# +2 is to the line terminators - b'\r\n' 

value = yield self.icap_connection.read_bytes(chunk_size + 2) 

value = value[:-2] 

if value == b'': 

self.eob = True 

raise tornado.gen.Return(value) 

 

@tornado.gen.coroutine 

def write_chunk(self, data=b''): 

"""Write a chunk of data 

 

When finished writing, an empty chunk with data='' must 

be written. 

""" 

l = hex(len(data))[2:].encode('utf-8') 

yield self.icap_connection.write_chunk(l + b'\r\n' + data + b'\r\n') 

 

@tornado.gen.coroutine 

def cont(self): 

"""Send a 100 continue reply 

 

Useful when the client sends a preview request, and we have 

to read the entire message body. After this command, read_chunk 

can safely be called again. 

""" 

if self.ieof: 

raise ICAPError(500, 'Tried to continue on ieof condition') 

 

yield self.icap_connection.write_chunk(b'ICAP/1.0 100 Continue\r\n\r\n') 

self.eob = False 

 

def set_enc_status(self, status): 

"""Set encapsulated status in response 

 

ICAP responses can only contain one encapsulated header section. 

Such section is either an encapsulated HTTP request, or a 

response. This method can be called to set encapsulated HTTP 

response's status line. 

""" 

# TODO: some semantic checking might be OK 

self.enc_status = status 

 

def set_enc_request(self, request): 

"""Set encapsulated request line in response 

 

ICAP responses can only contain one encapsulated header section. 

Such section is either an encapsulated HTTP request, or a 

response. This method can be called to set encapsulated HTTP 

request's request line. 

""" 

# TODO: some semantic checking might be OK 

self.enc_request = request 

 

def set_enc_header(self, header, value): 

"""Set an encapsulated header to the given value 

 

Multiple sets will cause the header to be sent multiple times. 

""" 

self.enc_headers[header] = self.enc_headers.get(header, []) + [value] 

 

def set_icap_response(self, code, message=None): 

"""Sets the ICAP response's status line and response code""" 

self.icap_response = b'ICAP/1.0 ' + str(code).encode('utf-8') + b' ' + \ 

(message if message else self._responses[code][0]) 

self.icap_response_code = code 

 

def set_icap_header(self, header, value): 

"""Set an ICAP header to the given value 

 

Multiple sets will cause the header to be sent multiple times. 

""" 

self.icap_headers[header] = self.icap_headers.get(header, []) + [value] 

 

@tornado.gen.coroutine 

def send_headers(self, has_body=False): 

"""Send ICAP and encapsulated headers 

 

Assembles the Encapsulated header, so it's need the information 

of wether an encapsulated message body is present. 

""" 

enc_header = None 

enc_req_stat = b'' 

enc_body = b'' 

if self.enc_request is not None: 

enc_header = b'req-hdr=0' 

enc_body = b'req-body=' 

enc_req_stat = self.enc_request + b'\r\n' 

elif self.enc_status is not None: 

enc_header = b'res-hdr=0' 

enc_body = b'res-body=' 

enc_req_stat = self.enc_status + b'\r\n' 

 

if not has_body: 

enc_body = b'null-body=' 

 

if b'istag' not in self.icap_headers: 

s_type = string.ascii_letters + string.digits 

s_msg = ''.join(random.choice(s_type) for _ in range(30)) 

self.set_icap_header(b'istag', ('"{0}"'.format(s_msg).encode('utf-8'))) 

 

if b'date' not in self.icap_headers: 

self.set_icap_header(b'date', self.date_time_bytes()) 

 

if b'server' not in self.icap_headers: 

self.set_icap_header(b'server', self.version_bytes()) 

 

enc_header_str = enc_req_stat 

for k in self.enc_headers: 

for v in self.enc_headers[k]: 

enc_header_str += _normalized_headers[k] + b': ' + v + b'\r\n' 

if enc_header_str != b'': 

enc_header_str += b'\r\n' 

 

body_offset = len(enc_header_str) 

 

if enc_header: 

enc = enc_header + b', ' + enc_body + str(body_offset).encode('utf-8') 

self.set_icap_header(b'Encapsulated', enc) 

 

if b'connection' not in self.icap_headers and self.close_connection: 

self.set_icap_header(b'connection', b'close') 

 

icap_header_str = b'' 

for k in self.icap_headers: 

for v in self.icap_headers[k]: 

icap_header_str += _normalized_headers[k] + b': ' + v + b'\r\n' 

if k.lower() == b'connection' and v.lower() == b'close': 

self.close_connection = True 

if k.lower() == b'connection' and v.lower() == b'keep-alive': 

self.close_connection = False 

 

icap_header_str += b'\r\n' 

 

yield self.icap_connection.write_chunk(self.icap_response + b'\r\n' + 

icap_header_str + enc_header_str) 

 

@tornado.gen.coroutine 

def parse_request(self, raw_lines): # pylint: disable=too-many-branches 

"""Parse a request (internal).""" 

self.command = None 

self.request_version = 'ICAP/1.0' 

 

raw_lines = raw_lines.split('\r\n') 

raw_requestline = raw_lines.pop(0) 

self.requestline = raw_requestline.rstrip(b'\r\n') 

 

words = self.requestline.split() 

if len(words) != 3: 

raise ICAPError(400, "Bad request syntax (%r)" % self.requestline) 

 

command, request_uri, version = words 

 

if version[:5] != b'ICAP/': 

raise ICAPError(400, "Bad request protocol, only accepting ICAP") 

 

if command not in (b'OPTIONS', b'REQMOD', b'RESPMOD'): 

raise ICAPError(501, "command %r is not implemented" % command) 

 

try: 

base_version_number = version.split(b'/', 1)[1] 

version_number = base_version_number.split(b".") 

# RFC 2145 section 3.1 says there can be only one "." and 

# - major and minor numbers MUST be treated as 

# separate integers; 

# - ICAP/2.4 is a lower version than ICAP/2.13, which in 

# turn is lower than ICAP/12.3; 

# - Leading zeros MUST be ignored by recipients. 

if len(version_number) != 2: 

raise ValueError 

version_number = int(version_number[0]), int(version_number[1]) 

except (ValueError, IndexError): 

raise ICAPError(400, "Bad request version (%r)" % version) 

 

if version_number != (1, 0): 

raise ICAPError( 

505, "Invalid ICAP Version (%s)" % base_version_number 

) 

 

self.command, self.request_uri, self.request_version = \ 

command, request_uri, version 

 

# Examine the headers and look for a Connection directive 

self.headers = self._read_headers(raw_lines) 

 

conntype = self.headers.get(b'connection', [b''])[0] 

if conntype.lower() == b'close': 

self.close_connection = True 

 

self.encapsulated = {} 

if self.command in [b'RESPMOD', b'REQMOD']: 

for enc in self.headers.get(b'encapsulated', [b''])[0].split(b','): 

# TODO: raise ICAPError if Encapsulated is malformed or empty 

k, v = enc.strip().split(b'=') 

self.encapsulated[k] = int(v) 

 

self.preview = self.headers.get(b'preview', [None])[0] 

self.allow = [ 

x.strip() for x in self.headers.get(b'allow', [b''])[0].split(b',') 

] 

self.client_ip = self.headers.get( 

b'x-client-ip', [b'No X-Client-IP header'])[0] 

 

if self.command == b'REQMOD': 

if b'req-hdr' in self.encapsulated: 

hdr_lines = yield self.icap_connection.read_chunk() 

hdr_lines = hdr_lines.split('\r\n') 

self.enc_req = self._read_request(hdr_lines.pop(0)) 

self.enc_req_headers = self._read_headers(hdr_lines) 

if b'req-body' in self.encapsulated: 

self.has_body = True 

elif self.command == b'RESPMOD': 

if b'req-hdr' in self.encapsulated: 

hdr_lines = yield self.icap_connection.read_chunk() 

hdr_lines = hdr_lines.split('\r\n') 

self.enc_req = self._read_request(hdr_lines.pop(0)) 

self.enc_req_headers = self._read_headers(hdr_lines) 

if b'res-hdr' in self.encapsulated: 

hdr_lines = yield self.icap_connection.read_chunk() 

hdr_lines = hdr_lines.split('\r\n') 

self.enc_res_status = self._read_request(hdr_lines.pop(0)) 

self.enc_res_headers = self._read_headers(hdr_lines) 

if b'res-body' in self.encapsulated: 

self.has_body = True 

# Else: OPTIONS. No encapsulation. 

 

# Parse service name 

self.servicename = urlparse.urlparse(self.request_uri)[2].strip(b'/') 

 

@tornado.gen.coroutine 

def handle_request(self): 

"""Handle a single ICAP request. 

 

You normally don't need to override this method; see the class 

__doc__ string for information on how to handle specific HTTP 

commands such as GET and POST. 

 

""" 

try: 

raw_lines = yield self.icap_connection.read_chunk() 

if not raw_lines: 

self.close_connection = True 

return 

 

yield self.parse_request(raw_lines) 

 

method = self._icap_methods_map.get(self.command) 

yield method() 

except socket.timeout as e: 

self.log_error("Request timed out: %r", e) 

self.close_connection = True 

except ICAPError as e: 

self.log_error("ICAP error: %r", e) 

self.send_error(e.code, e.message[0]) 

# except: 

# self.send_error(500) 

 

@tornado.gen.coroutine 

def send_error(self, code, message=None): 

"""Send and log an error reply. 

 

Arguments are the error code, and a detailed message. 

The detailed message defaults to the short entry matching the 

response code. 

 

This sends an error response (so it must be called before any 

output has been generated), logs the error, and finally sends 

a piece of HTML explaining the error to the user. 

 

""" 

 

if message is None: 

message = self._responses[code][0] 

self.log_error("code %d, message %s", code, message) 

 

# No encapsulation 

self.enc_req = None 

self.enc_res_stats = None 

 

self.set_icap_response(code, message=message) 

self.set_icap_header(b'Connection', b'close') # TODO: why? 

yield self.send_headers() 

 

@tornado.gen.coroutine 

def send_enc_error(self, code, message=None, body='', 

contenttype='text/html'): 

"""Send an encapsulated error reply. 

 

Arguments are the error code, and a detailed message. 

The detailed message defaults to the short entry matching the 

response code. 

 

This sends an encapsulated error response (so it must be called 

before any output has been generated), logs the error, and 

finally sends a piece of HTML explaining the error to the user. 

""" 

 

# No encapsulation 

self.enc_req = None 

 

self.set_icap_response(200, message=message) 

self.set_enc_status('HTTP/1.1 %s %s' % (str(code).encode('utf-8'), message)) 

self.set_enc_header('Content-Type', contenttype) 

self.set_enc_header('Content-Length', str(len(body)).encode('utf-8')) 

yield self.send_headers(has_body=True) 

if body: 

yield self.write_chunk(body) 

yield self.write_chunk('') 

 

def log_error(self, format_str, *args): 

"""Log an error. 

 

This is called when a request cannot be fulfilled. 

""" 

logger.error({'msg': '%s' % (format_str % args)}, event='pyicap_error') 

 

def version_bytes(self): 

"""Return the server software version string.""" 

return (self._server_version + ' ' + self._sys_version).encode('utf-8') 

 

def date_time_bytes(self, timestamp=None): 

"""Return the current date and time formatted for a message header.""" 

if timestamp is None: 

timestamp = time.time() 

year, month, day, hh, mm, ss, wd, _, _ = time.gmtime(timestamp) 

s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % ( 

self._weekdayname[wd], 

day, self._monthname[month], year, 

hh, mm, ss) 

return s.encode('utf-8') 

 

def log_date_time_string(self): 

"""Return the current time formatted for logging.""" 

now = time.time() 

year, month, day, hh, mm, ss, _, _, _ = time.localtime(now) 

s = "%02d/%3s/%04d %02d:%02d:%02d" % ( 

day, self._monthname[month], year, hh, mm, ss) 

return s 

 

@tornado.gen.coroutine 

def no_adaptation_required(self): 

"""Tells the client to leave the message unaltered 

 

If the client allows 204, or this is a preview request than 

a 204 preview response is sent. Otherwise a copy of the message 

is returned to the client. 

""" 

if b'204' in self.allow or self.preview is not None: 

# We MUST read everything the client sent us 

if self.has_body: 

while True: 

data = yield self.read_chunk() 

if data == b'': 

break 

self.set_icap_response(204) 

yield self.send_headers() 

else: 

# We have to copy everything, 

# but it's sure there's no preview 

self.set_icap_response(200) 

 

if self.enc_res_status is not None: 

self.set_enc_status(b' '.join(self.enc_res_status)) 

for h in self.enc_res_headers: 

for v in self.enc_res_headers[h]: 

self.set_enc_header(h, v) 

 

if not self.has_body: 

yield self.send_headers(False) 

return 

 

yield self.send_headers(True) 

while True: 

chunk = yield self.read_chunk() 

yield self.write_chunk(chunk) 

if chunk == b'': 

break 

 

@tornado.gen.coroutine 

def icap_options(self): 

"""Called on OPTIONS icap meesage""" 

raise NotImplementedError() 

 

@tornado.gen.coroutine 

def icap_reqmod(self): 

"""Called on REQMOD icap meesage""" 

raise NotImplementedError() 

 

@tornado.gen.coroutine 

def icap_respmod(self): 

"""Called on RESPMOD icap meesage""" 

raise NotImplementedError()