1
2
3
4
5
6
7
8
9
10
11
12
13
14 package org.eclipse.jetty.http;
15
16 import java.io.IOException;
17
18 import org.eclipse.jetty.io.Buffer;
19 import org.eclipse.jetty.io.BufferUtil;
20 import org.eclipse.jetty.io.Buffers;
21 import org.eclipse.jetty.io.ByteArrayBuffer;
22 import org.eclipse.jetty.io.EndPoint;
23 import org.eclipse.jetty.io.EofException;
24 import org.eclipse.jetty.io.BufferCache.CachedBuffer;
25 import org.eclipse.jetty.util.StringUtil;
26 import org.eclipse.jetty.util.log.Log;
27
28
29
30
31
32
33
34
35 public class HttpGenerator extends AbstractGenerator
36 {
37
38 private static class Status
39 {
40 Buffer _reason;
41 Buffer _schemeCode;
42 Buffer _responseLine;
43 }
44 private static Status[] __status = new Status[HttpStatus.MAX_CODE+1];
45 static
46 {
47 int versionLength=HttpVersions.HTTP_1_1_BUFFER.length();
48
49 for (int i=0;i<__status.length;i++)
50 {
51 HttpStatus.Code code = HttpStatus.getCode(i);
52 if (code==null)
53 continue;
54 String reason=code.getMessage();
55 byte[] bytes=new byte[versionLength+5+reason.length()+2];
56 HttpVersions.HTTP_1_1_BUFFER.peek(0,bytes, 0, versionLength);
57 bytes[versionLength+0]=' ';
58 bytes[versionLength+1]=(byte)('0'+i/100);
59 bytes[versionLength+2]=(byte)('0'+(i%100)/10);
60 bytes[versionLength+3]=(byte)('0'+(i%10));
61 bytes[versionLength+4]=' ';
62 for (int j=0;j<reason.length();j++)
63 bytes[versionLength+5+j]=(byte)reason.charAt(j);;
64 bytes[versionLength+5+reason.length()]=HttpTokens.CARRIAGE_RETURN;
65 bytes[versionLength+6+reason.length()]=HttpTokens.LINE_FEED;
66
67 __status[i] = new Status();
68 __status[i]._reason=new ByteArrayBuffer(bytes,versionLength+5,bytes.length-versionLength-7,Buffer.IMMUTABLE);
69 __status[i]._schemeCode=new ByteArrayBuffer(bytes,0,versionLength+5,Buffer.IMMUTABLE);
70 __status[i]._responseLine=new ByteArrayBuffer(bytes,0,bytes.length,Buffer.IMMUTABLE);
71 }
72 }
73
74
75 public static Buffer getReasonBuffer(int code)
76 {
77 Status status = code<__status.length?__status[code]:null;
78 if (status!=null)
79 return status._reason;
80 return null;
81 }
82
83
84
85 private static byte[] LAST_CHUNK =
86 { (byte) '0', (byte) '\015', (byte) '\012', (byte) '\015', (byte) '\012'};
87 private static byte[] CONTENT_LENGTH_0 = StringUtil.getBytes("Content-Length: 0\015\012");
88 private static byte[] CONNECTION_KEEP_ALIVE = StringUtil.getBytes("Connection: keep-alive\015\012");
89 private static byte[] CONNECTION_CLOSE = StringUtil.getBytes("Connection: close\015\012");
90 private static byte[] CONNECTION_ = StringUtil.getBytes("Connection: ");
91 private static byte[] CRLF = StringUtil.getBytes("\015\012");
92 private static byte[] TRANSFER_ENCODING_CHUNKED = StringUtil.getBytes("Transfer-Encoding: chunked\015\012");
93 private static byte[] SERVER = StringUtil.getBytes("Server: Jetty(7.0.x)\015\012");
94
95
96 private static int CHUNK_SPACE = 12;
97
98 public static void setServerVersion(String version)
99 {
100 SERVER=StringUtil.getBytes("Server: Jetty("+version+")\015\012");
101 }
102
103
104 private boolean _bypass = false;
105 private boolean _needCRLF = false;
106 private boolean _needEOC = false;
107 private boolean _bufferChunked = false;
108
109
110
111
112
113
114
115
116
117
118 public HttpGenerator(Buffers buffers, EndPoint io, int headerBufferSize, int contentBufferSize)
119 {
120 super(buffers,io,headerBufferSize,contentBufferSize);
121 }
122
123
124 public void reset(boolean returnBuffers)
125 {
126 super.reset(returnBuffers);
127 _bypass = false;
128 _needCRLF = false;
129 _needEOC = false;
130 _bufferChunked=false;
131 _method=null;
132 _uri=null;
133 _noContent=false;
134 }
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149 public void addContent(Buffer content, boolean last) throws IOException
150 {
151 if (_noContent)
152 throw new IllegalStateException("NO CONTENT");
153
154 if (_last || _state==STATE_END)
155 {
156 Log.debug("Ignoring extra content {}",content);
157 content.clear();
158 return;
159 }
160 _last = last;
161
162
163 if (_content!=null && _content.length()>0 || _bufferChunked)
164 {
165 if (!_endp.isOpen())
166 throw new EofException();
167 flushBuffer();
168 if (_content != null && _content.length()>0 || _bufferChunked)
169 throw new IllegalStateException("FULL");
170 }
171
172 _content = content;
173 _contentWritten += content.length();
174
175
176 if (_head)
177 {
178 content.clear();
179 _content=null;
180 }
181 else if (_endp != null && _buffer == null && content.length() > 0 && _last)
182 {
183
184
185 _bypass = true;
186 }
187 else
188 {
189
190 if (_buffer == null)
191 _buffer = _buffers.getBuffer(_contentBufferSize);
192
193
194 int len=_buffer.put(_content);
195 _content.skip(len);
196 if (_content.length() == 0)
197 _content = null;
198 }
199 }
200
201
202
203
204
205
206
207 public void sendResponse(Buffer response) throws IOException
208 {
209 if (_noContent || _state!=STATE_HEADER || _content!=null && _content.length()>0 || _bufferChunked || _head )
210 throw new IllegalStateException();
211
212 _last = true;
213
214 _content = response;
215 _bypass = true;
216 _state = STATE_FLUSHING;
217
218
219 _contentLength =_contentWritten = response.length();
220
221 }
222
223
224
225
226
227
228
229
230
231 public boolean addContent(byte b) throws IOException
232 {
233 if (_noContent)
234 throw new IllegalStateException("NO CONTENT");
235
236 if (_last || _state==STATE_END)
237 {
238 Log.debug("Ignoring extra content {}",new Byte(b));
239 return false;
240 }
241
242
243 if (_content != null && _content.length()>0 || _bufferChunked)
244 {
245 flushBuffer();
246 if (_content != null && _content.length()>0 || _bufferChunked)
247 throw new IllegalStateException("FULL");
248 }
249
250 _contentWritten++;
251
252
253 if (_head)
254 return false;
255
256
257 if (_buffer == null)
258 _buffer = _buffers.getBuffer(_contentBufferSize);
259
260
261 _buffer.put(b);
262
263 return _buffer.space()<=(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0);
264 }
265
266
267
268
269
270
271
272 public int prepareUncheckedAddContent() throws IOException
273 {
274 if (_noContent)
275 return -1;
276
277 if (_last || _state==STATE_END)
278 return -1;
279
280
281 Buffer content = _content;
282 if (content != null && content.length()>0 || _bufferChunked)
283 {
284 flushBuffer();
285 if (content != null && content.length()>0 || _bufferChunked)
286 throw new IllegalStateException("FULL");
287 }
288
289
290 if (_buffer == null)
291 _buffer = _buffers.getBuffer(_contentBufferSize);
292
293 _contentWritten-=_buffer.length();
294
295
296 if (_head)
297 return Integer.MAX_VALUE;
298
299 return _buffer.space()-(_contentLength == HttpTokens.CHUNKED_CONTENT?CHUNK_SPACE:0);
300 }
301
302
303 public boolean isBufferFull()
304 {
305
306 boolean full = super.isBufferFull() || _bufferChunked || _bypass || (_contentLength == HttpTokens.CHUNKED_CONTENT && _buffer != null && _buffer.space() < CHUNK_SPACE);
307 return full;
308 }
309
310
311 public void completeHeader(HttpFields fields, boolean allContentAdded) throws IOException
312 {
313 if (_state != STATE_HEADER)
314 return;
315
316
317 if (_method==null && _status==0)
318 throw new EofException();
319
320 if (_last && !allContentAdded)
321 throw new IllegalStateException("last?");
322 _last = _last | allContentAdded;
323
324
325 if (_header == null)
326 _header = _buffers.getBuffer(_headerBufferSize);
327
328 boolean has_server = false;
329
330 if (_method!=null)
331 {
332 _close = false;
333
334 if (_version == HttpVersions.HTTP_0_9_ORDINAL)
335 {
336 _contentLength = HttpTokens.NO_CONTENT;
337 _header.put(_method);
338 _header.put((byte)' ');
339 _header.put(_uri.getBytes("utf-8"));
340 _header.put(HttpTokens.CRLF);
341 _state = STATE_FLUSHING;
342 _noContent=true;
343 return;
344 }
345 else
346 {
347 _header.put(_method);
348 _header.put((byte)' ');
349 _header.put(_uri.getBytes("utf-8"));
350 _header.put((byte)' ');
351 _header.put(_version==HttpVersions.HTTP_1_0_ORDINAL?HttpVersions.HTTP_1_0_BUFFER:HttpVersions.HTTP_1_1_BUFFER);
352 _header.put(HttpTokens.CRLF);
353 }
354 }
355 else
356 {
357
358 if (_version == HttpVersions.HTTP_0_9_ORDINAL)
359 {
360 _close = true;
361 _contentLength = HttpTokens.EOF_CONTENT;
362 _state = STATE_CONTENT;
363 return;
364 }
365 else
366 {
367 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
368 _close = true;
369
370
371 Status status = _status<__status.length?__status[_status]:null;
372
373 if (status==null)
374 {
375 _header.put(HttpVersions.HTTP_1_1_BUFFER);
376 _header.put((byte) ' ');
377 _header.put((byte) ('0' + _status / 100));
378 _header.put((byte) ('0' + (_status % 100) / 10));
379 _header.put((byte) ('0' + (_status % 10)));
380 _header.put((byte) ' ');
381 if (_reason==null)
382 {
383 _header.put((byte) ('0' + _status / 100));
384 _header.put((byte) ('0' + (_status % 100) / 10));
385 _header.put((byte) ('0' + (_status % 10)));
386 }
387 else
388 _header.put(_reason);
389 _header.put(HttpTokens.CRLF);
390 }
391 else
392 {
393 if (_reason==null)
394 _header.put(status._responseLine);
395 else
396 {
397 _header.put(status._schemeCode);
398 _header.put(_reason);
399 _header.put(HttpTokens.CRLF);
400 }
401 }
402
403 if (_status<200 && _status>=100 )
404 {
405 _noContent=true;
406 _content=null;
407 if (_buffer!=null)
408 _buffer.clear();
409
410 _header.put(HttpTokens.CRLF);
411 _state = STATE_CONTENT;
412 return;
413 }
414
415 if (_status==204 || _status==304)
416 {
417 _noContent=true;
418 _content=null;
419 if (_buffer!=null)
420 _buffer.clear();
421 }
422 }
423 }
424
425
426
427
428 HttpFields.Field content_length = null;
429 HttpFields.Field transfer_encoding = null;
430 boolean keep_alive = false;
431 boolean close=false;
432 StringBuilder connection = null;
433
434 if (fields != null)
435 {
436 int s=fields.size();
437 for (int f=0;f<s;f++)
438 {
439 HttpFields.Field field = fields.getField(f);
440 if (field==null)
441 continue;
442 switch (field.getNameOrdinal())
443 {
444 case HttpHeaders.CONTENT_LENGTH_ORDINAL:
445 content_length = field;
446 _contentLength = field.getLongValue();
447
448 if (_contentLength < _contentWritten || _last && _contentLength != _contentWritten)
449 content_length = null;
450
451
452 field.put(_header);
453 break;
454
455 case HttpHeaders.CONTENT_TYPE_ORDINAL:
456 if (BufferUtil.isPrefix(MimeTypes.MULTIPART_BYTERANGES_BUFFER, field.getValueBuffer())) _contentLength = HttpTokens.SELF_DEFINING_CONTENT;
457
458
459 field.put(_header);
460 break;
461
462 case HttpHeaders.TRANSFER_ENCODING_ORDINAL:
463 if (_version == HttpVersions.HTTP_1_1_ORDINAL) transfer_encoding = field;
464
465 break;
466
467 case HttpHeaders.CONNECTION_ORDINAL:
468 if (_method!=null)
469 field.put(_header);
470
471 int connection_value = field.getValueOrdinal();
472 switch (connection_value)
473 {
474 case -1:
475 {
476 String[] values = field.getValue().split(",");
477 for (int i=0;values!=null && i<values.length;i++)
478 {
479 CachedBuffer cb = HttpHeaderValues.CACHE.get(values[i].trim());
480
481 if (cb!=null)
482 {
483 switch(cb.getOrdinal())
484 {
485 case HttpHeaderValues.CLOSE_ORDINAL:
486 close=true;
487 if (_method==null)
488 _close=true;
489 keep_alive=false;
490 if (_close && _method==null && _contentLength == HttpTokens.UNKNOWN_CONTENT)
491 _contentLength = HttpTokens.EOF_CONTENT;
492 break;
493
494 case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
495 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
496 {
497 keep_alive = true;
498 if (_method==null)
499 _close = false;
500 }
501 break;
502
503 default:
504 if (connection==null)
505 connection=new StringBuilder();
506 else
507 connection.append(',');
508 connection.append(values[i]);
509 }
510 }
511 else
512 {
513 if (connection==null)
514 connection=new StringBuilder();
515 else
516 connection.append(',');
517 connection.append(values[i]);
518 }
519 }
520
521 break;
522 }
523 case HttpHeaderValues.CLOSE_ORDINAL:
524 {
525 close=true;
526 if (_method==null)
527 _close=true;
528 if (_close && _method==null && _contentLength == HttpTokens.UNKNOWN_CONTENT)
529 _contentLength = HttpTokens.EOF_CONTENT;
530 break;
531 }
532 case HttpHeaderValues.KEEP_ALIVE_ORDINAL:
533 {
534 if (_version == HttpVersions.HTTP_1_0_ORDINAL)
535 {
536 keep_alive = true;
537 if (_method==null)
538 _close = false;
539 }
540 break;
541 }
542 default:
543 {
544 if (connection==null)
545 connection=new StringBuilder();
546 else
547 connection.append(',');
548 connection.append(field.getValue());
549 }
550 }
551
552
553 break;
554
555 case HttpHeaders.SERVER_ORDINAL:
556 if (getSendServerVersion())
557 {
558 has_server=true;
559 field.put(_header);
560 }
561 break;
562
563 default:
564
565 field.put(_header);
566 }
567 }
568 }
569
570
571
572
573
574
575
576
577
578
579 switch ((int) _contentLength)
580 {
581 case HttpTokens.UNKNOWN_CONTENT:
582
583
584
585
586 if (_contentWritten == 0 && _method==null && (_status < 200 || _status == 204 || _status == 304))
587 _contentLength = HttpTokens.NO_CONTENT;
588 else if (_last)
589 {
590
591 _contentLength = _contentWritten;
592 if (content_length == null)
593 {
594
595 _header.put(HttpHeaders.CONTENT_LENGTH_BUFFER);
596 _header.put(HttpTokens.COLON);
597 _header.put((byte) ' ');
598 BufferUtil.putDecLong(_header, _contentLength);
599 _header.put(HttpTokens.CRLF);
600 }
601 }
602 else
603 {
604
605 _contentLength = (_close || _version < HttpVersions.HTTP_1_1_ORDINAL ) ? HttpTokens.EOF_CONTENT : HttpTokens.CHUNKED_CONTENT;
606 if (_method!=null && _contentLength==HttpTokens.EOF_CONTENT)
607 {
608 _contentLength=HttpTokens.NO_CONTENT;
609 _noContent=true;
610 }
611 }
612 break;
613
614 case HttpTokens.NO_CONTENT:
615 if (content_length == null && _method==null && _status >= 200 && _status != 204 && _status != 304)
616 _header.put(CONTENT_LENGTH_0);
617 break;
618
619 case HttpTokens.EOF_CONTENT:
620 _close = _method==null;
621 break;
622
623 case HttpTokens.CHUNKED_CONTENT:
624 break;
625
626 default:
627
628 break;
629 }
630
631
632 if (_contentLength == HttpTokens.CHUNKED_CONTENT)
633 {
634
635 if (transfer_encoding != null && HttpHeaderValues.CHUNKED_ORDINAL != transfer_encoding.getValueOrdinal())
636 {
637 String c = transfer_encoding.getValue();
638 if (c.endsWith(HttpHeaderValues.CHUNKED))
639 transfer_encoding.put(_header);
640 else
641 throw new IllegalArgumentException("BAD TE");
642 }
643 else
644 _header.put(TRANSFER_ENCODING_CHUNKED);
645 }
646
647
648 if (_contentLength==HttpTokens.EOF_CONTENT)
649 {
650 keep_alive=false;
651 _close=true;
652 }
653
654 if (_method==null)
655 {
656 if (_close && (close || _version > HttpVersions.HTTP_1_0_ORDINAL))
657 {
658 _header.put(CONNECTION_CLOSE);
659 if (connection!=null)
660 {
661 _header.setPutIndex(_header.putIndex()-2);
662 _header.put((byte)',');
663 _header.put(connection.toString().getBytes());
664 _header.put(CRLF);
665 }
666 }
667 else if (keep_alive)
668 {
669 _header.put(CONNECTION_KEEP_ALIVE);
670 if (connection!=null)
671 {
672 _header.setPutIndex(_header.putIndex()-2);
673 _header.put((byte)',');
674 _header.put(connection.toString().getBytes());
675 _header.put(CRLF);
676 }
677 }
678 else if (connection!=null)
679 {
680 _header.put(CONNECTION_);
681 _header.put(connection.toString().getBytes());
682 _header.put(CRLF);
683 }
684 }
685
686 if (!has_server && _status>100 && getSendServerVersion())
687 _header.put(SERVER);
688
689
690 _header.put(HttpTokens.CRLF);
691
692 _state = STATE_CONTENT;
693
694 }
695
696
697
698
699
700
701
702 public void complete() throws IOException
703 {
704 if (_state == STATE_END)
705 return;
706
707 super.complete();
708
709 if (_state < STATE_FLUSHING)
710 {
711 _state = STATE_FLUSHING;
712 if (_contentLength == HttpTokens.CHUNKED_CONTENT)
713 _needEOC = true;
714 }
715
716 flushBuffer();
717 }
718
719
720 public long flushBuffer() throws IOException
721 {
722 try
723 {
724 if (_state == STATE_HEADER)
725 throw new IllegalStateException("State==HEADER");
726
727 prepareBuffers();
728
729 if (_endp == null)
730 {
731 if (_needCRLF && _buffer!=null)
732 _buffer.put(HttpTokens.CRLF);
733 if (_needEOC && _buffer!=null && !_head)
734 _buffer.put(LAST_CHUNK);
735 _needCRLF=false;
736 _needEOC=false;
737 return 0;
738 }
739
740 int total= 0;
741
742 int len = -1;
743 int to_flush = ((_header != null && _header.length() > 0)?4:0) | ((_buffer != null && _buffer.length() > 0)?2:0) | ((_bypass && _content != null && _content.length() > 0)?1:0);
744 switch (to_flush)
745 {
746 case 7:
747 throw new IllegalStateException();
748 case 6:
749 len = _endp.flush(_header, _buffer, null);
750 break;
751 case 5:
752 len = _endp.flush(_header, _content, null);
753 break;
754 case 4:
755 len = _endp.flush(_header);
756 break;
757 case 3:
758 throw new IllegalStateException();
759 case 2:
760 len = _endp.flush(_buffer);
761 break;
762 case 1:
763 len = _endp.flush(_content);
764 break;
765 case 0:
766 {
767
768 if (_header != null)
769 _header.clear();
770
771 _bypass = false;
772 _bufferChunked = false;
773
774 if (_buffer != null)
775 {
776 _buffer.clear();
777 if (_contentLength == HttpTokens.CHUNKED_CONTENT)
778 {
779
780 _buffer.setPutIndex(CHUNK_SPACE);
781 _buffer.setGetIndex(CHUNK_SPACE);
782
783
784
785 if (_content != null && _content.length() < _buffer.space() && _state != STATE_FLUSHING)
786 {
787 _buffer.put(_content);
788 _content.clear();
789 _content = null;
790 }
791 }
792 }
793
794
795 if (!_needCRLF && !_needEOC && (_content == null || _content.length() == 0))
796 {
797 if (_state == STATE_FLUSHING)
798 _state = STATE_END;
799 if (_state==STATE_END && _close && _status!=100)
800 _endp.close();
801 }
802 else
803
804 prepareBuffers();
805 }
806 }
807
808 if (len > 0)
809 total+=len;
810
811 return total;
812 }
813 catch (IOException e)
814 {
815 Log.ignore(e);
816 throw (e instanceof EofException) ? e:new EofException(e);
817 }
818 }
819
820
821 private void prepareBuffers()
822 {
823
824 if (!_bufferChunked)
825 {
826
827 if (_content != null && _content.length() > 0 && _buffer != null && _buffer.space() > 0)
828 {
829 int len = _buffer.put(_content);
830 _content.skip(len);
831 if (_content.length() == 0)
832 _content = null;
833 }
834
835
836 if (_contentLength == HttpTokens.CHUNKED_CONTENT)
837 {
838 int size = _buffer == null ? 0 : _buffer.length();
839 if (size > 0)
840 {
841
842 _bufferChunked = true;
843
844
845 if (_buffer.getIndex() == CHUNK_SPACE)
846 {
847
848 _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
849 _buffer.setGetIndex(_buffer.getIndex() - 2);
850 BufferUtil.prependHexInt(_buffer, size);
851
852 if (_needCRLF)
853 {
854 _buffer.poke(_buffer.getIndex() - 2, HttpTokens.CRLF, 0, 2);
855 _buffer.setGetIndex(_buffer.getIndex() - 2);
856 _needCRLF = false;
857 }
858 }
859 else
860 {
861
862 if (_needCRLF)
863 {
864 if (_header.length() > 0) throw new IllegalStateException("EOC");
865 _header.put(HttpTokens.CRLF);
866 _needCRLF = false;
867 }
868 BufferUtil.putHexInt(_header, size);
869 _header.put(HttpTokens.CRLF);
870 }
871
872
873 if (_buffer.space() >= 2)
874 _buffer.put(HttpTokens.CRLF);
875 else
876 _needCRLF = true;
877 }
878
879
880 if (_needEOC && (_content == null || _content.length() == 0))
881 {
882 if (_needCRLF)
883 {
884 if (_buffer == null && _header.space() >= 2)
885 {
886 _header.put(HttpTokens.CRLF);
887 _needCRLF = false;
888 }
889 else if (_buffer!=null && _buffer.space() >= 2)
890 {
891 _buffer.put(HttpTokens.CRLF);
892 _needCRLF = false;
893 }
894 }
895
896 if (!_needCRLF && _needEOC)
897 {
898 if (_buffer == null && _header.space() >= LAST_CHUNK.length)
899 {
900 if (!_head)
901 {
902 _header.put(LAST_CHUNK);
903 _bufferChunked=true;
904 }
905 _needEOC = false;
906 }
907 else if (_buffer!=null && _buffer.space() >= LAST_CHUNK.length)
908 {
909 if (!_head)
910 {
911 _buffer.put(LAST_CHUNK);
912 _bufferChunked=true;
913 }
914 _needEOC = false;
915 }
916 }
917 }
918 }
919 }
920
921 if (_content != null && _content.length() == 0)
922 _content = null;
923
924 }
925
926 public int getBytesBuffered()
927 {
928 return(_header==null?0:_header.length())+
929 (_buffer==null?0:_buffer.length())+
930 (_content==null?0:_content.length());
931 }
932
933 public boolean isEmpty()
934 {
935 return (_header==null||_header.length()==0) &&
936 (_buffer==null||_buffer.length()==0) &&
937 (_content==null||_content.length()==0);
938 }
939
940 public String toString()
941 {
942 return "HttpGenerator s="+_state+
943 " h="+(_header==null?"null":_header.length())+
944 " b="+(_buffer==null?"null":_buffer.length())+
945 " c="+(_content==null?"null":_content.length());
946 }
947 }