track total size of static array and Unit/Class/Func
[hiphop-php.git] / hphp / runtime / base / file.cpp
blob4482d09b31878ba24e9643685e06c63dc97aa0ec
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/base/file.h"
19 #include "hphp/runtime/base/array-init.h"
20 #include "hphp/runtime/base/array-iterator.h"
21 #include "hphp/runtime/base/builtin-functions.h"
22 #include "hphp/runtime/base/exceptions.h"
23 #include "hphp/runtime/base/runtime-error.h"
24 #include "hphp/runtime/base/runtime-option.h"
25 #include "hphp/runtime/base/stream-wrapper-registry.h"
26 #include "hphp/runtime/base/string-buffer.h"
27 #include "hphp/runtime/base/request-info.h"
28 #include "hphp/runtime/base/zend-printf.h"
29 #include "hphp/runtime/base/zend-string.h"
31 #include "hphp/runtime/ext/stream/ext_stream.h"
33 #include "hphp/runtime/server/static-content-cache.h"
34 #include "hphp/runtime/server/virtual-host.h"
36 #include "hphp/runtime/base/file-util.h"
37 #include "hphp/util/logger.h"
38 #include "hphp/util/process.h"
40 #include <folly/String.h>
41 #include <folly/portability/Fcntl.h>
42 #include <folly/portability/SysFile.h>
44 #include <algorithm>
46 namespace HPHP {
48 const int FileData::DEFAULT_CHUNK_SIZE = 8192;
50 FileData::FileData(bool nonblocking)
51 : m_nonblocking(nonblocking)
52 { }
54 bool FileData::closeImpl() {
55 if (m_buffer != nullptr) {
56 free(m_buffer);
57 m_buffer = nullptr;
59 return true;
62 FileData::~FileData() {
63 FileData::closeImpl();
66 ///////////////////////////////////////////////////////////////////////////////
67 // statics
69 StaticString File::s_resource_name("stream");
71 RDS_LOCAL(int, s_pcloseRet);
73 const int File::USE_INCLUDE_PATH = 1;
75 String File::TranslatePathKeepRelative(const char* filename, uint32_t size) {
76 // canonicalize asserts that we don't have nulls
77 String canonicalized = FileUtil::canonicalize(filename, size);
78 if (RID().hasSafeFileAccess()) {
79 auto const& allowedDirectories = RID().getAllowedDirectoriesProcessed();
80 auto it = std::upper_bound(allowedDirectories.begin(),
81 allowedDirectories.end(), canonicalized,
82 [](const String& val, const std::string& dir) {
83 return strcmp(val.c_str(), dir.c_str()) < 0;
84 });
85 if (it != allowedDirectories.begin()) {
86 const std::string& dir = *--it;
87 if (dir.size() <= canonicalized.size() &&
88 !strncmp(dir.c_str(), canonicalized.c_str(), dir.size())) {
89 return canonicalized;
93 // disallow access with an absolute path
94 if (FileUtil::isAbsolutePath(canonicalized.slice())) {
95 return empty_string();
98 // unresolvable paths are all considered as unsafe
99 if (canonicalized.find("..") >= 0) {
100 assertx(canonicalized.find("..") == 0);
101 return empty_string();
105 return canonicalized;
108 String File::TranslatePath(const String& filename) {
109 if (filename.empty()) {
110 // Special case: an empty string should continue to be an empty string.
111 // Otherwise it would be canonicalized to CWD, which is inconsistent with
112 // PHP and most filesystem utilities.
113 return filename;
114 } else if (!FileUtil::isAbsolutePath(filename.slice())) {
115 String cwd = g_context->getCwd();
116 return TranslatePathKeepRelative(cwd + "/" + filename);
117 } else {
118 return TranslatePathKeepRelative(filename);
122 String File::TranslatePathWithFileCache(const String& filename) {
123 // canonicalize asserts that we don't have nulls
124 String canonicalized = FileUtil::canonicalize(filename);
125 String translated = TranslatePath(canonicalized);
126 if (!translated.empty() && access(translated.data(), F_OK) < 0 &&
127 StaticContentCache::TheFileCache) {
128 if (StaticContentCache::TheFileCache->exists(canonicalized.data(),
129 false)) {
130 // we use file cache's file name to make stat() work
131 translated = String(RuntimeOption::FileCache);
134 return translated;
137 String File::TranslateCommand(const String& cmd) {
138 //TODO: security checking
139 return cmd;
142 bool File::IsVirtualDirectory(const String& filename) {
143 return
144 StaticContentCache::TheFileCache &&
145 StaticContentCache::TheFileCache->dirExists(filename.data(), false);
148 bool File::IsVirtualFile(const String& filename) {
149 return
150 StaticContentCache::TheFileCache &&
151 StaticContentCache::TheFileCache->fileExists(filename.data(), false);
154 req::ptr<File> File::Open(const String& filename, const String& mode,
155 int options /* = 0 */,
156 const req::ptr<StreamContext>& context /* = null */) {
157 Stream::Wrapper *wrapper = Stream::getWrapperFromURI(filename);
158 if (!wrapper) return nullptr;
159 if (filename.find('\0') >= 0) return nullptr;
160 auto rcontext = context ? context : g_context->getStreamContext();
161 auto file = wrapper->open(filename, mode, options, rcontext);
162 if (file) {
163 file->m_data->m_name = filename.data();
164 file->m_streamContext = rcontext;
165 // Let the wrapper set the mode itself if needed.
166 if (file->m_data->m_mode.empty()) {
167 file->m_data->m_mode = mode.data();
170 return file;
173 ///////////////////////////////////////////////////////////////////////////////
174 // constructor and destructor
176 File::File(
177 std::shared_ptr<FileData> data,
178 const String& wrapper_type, /* = null_string */
179 const String& stream_type /* = empty_string_ref*/)
180 : m_data(data),
181 m_wrapperType(wrapper_type.get()),
182 m_streamType(stream_type.get())
185 File::File(bool nonblocking /* = true */,
186 const String& wrapper_type /* = null_string */,
187 const String& stream_type /* = empty_string_ref */)
188 : File(std::make_shared<FileData>(nonblocking), wrapper_type, stream_type)
191 File::~File() {
192 if(m_data.unique()) {
193 closeImpl();
195 m_data.reset();
198 void File::sweep() {
199 // Clear non-request-local state without deleting `this`. Therefore assumes
200 // `this` has been request-heap allocated. Note that the derived class'
201 // sweep() is responsible for closing m_fd and any other non-request
202 // resources it might have allocated.
203 assertx(!valid());
204 File::closeImpl();
205 m_data.reset();
206 m_wrapperType = nullptr;
207 m_streamType = nullptr;
210 bool File::closeImpl() {
211 return m_data ? m_data->closeImpl() : true;
214 ///////////////////////////////////////////////////////////////////////////////
215 // default implementation of virtual functions
217 int File::getc() {
218 if (m_data->m_writepos > m_data->m_readpos) {
219 m_data->m_position++;
220 return m_data->m_buffer[m_data->m_readpos++] & 0xff;
223 char buffer[1];
224 int64_t len = readImpl(buffer, 1);
225 if (len != 1) {
226 return EOF;
228 m_data->m_position += len;
229 return (int)(unsigned char)buffer[0];
232 String File::read() {
233 StringBuffer sb;
234 int64_t copied = 0;
235 int64_t avail = bufferedLen();
237 while (!eof() || avail) {
238 if (m_data->m_buffer == nullptr) {
239 m_data->m_buffer = (char *)malloc(m_data->m_chunkSize);
240 m_data->m_bufferSize = m_data->m_chunkSize;
243 if (avail > 0) {
244 sb.append(m_data->m_buffer + m_data->m_readpos, avail);
245 copied += avail;
248 m_data->m_writepos = readImpl(m_data->m_buffer, m_data->m_bufferSize);
249 m_data->m_readpos = 0;
250 avail = bufferedLen();
252 if (avail == 0) {
253 break;
257 m_data->m_position += copied;
258 return sb.detach();
261 String File::read(int64_t length) {
262 if (length <= 0) {
263 raise_notice("Invalid length %" PRId64, length);
264 // XXX: Changing this to empty_string causes problems, something is
265 // writing to this upstream but I'm not sure what and since it's
266 // unlikely to provide significant gain alone I'm leaving it for now.
267 return "";
270 auto const allocSize = length;
271 String s = String(allocSize, ReserveString);
272 char *ret = s.mutableData();
273 int64_t copied = 0;
274 int64_t avail = bufferedLen();
276 while (avail < length && !eof()) {
277 if (m_data->m_buffer == nullptr) {
278 m_data->m_buffer = (char *)malloc(m_data->m_chunkSize);
279 m_data->m_bufferSize = m_data->m_chunkSize;
282 if (avail > 0) {
283 memcpy(ret + copied, m_data->m_buffer + m_data->m_readpos, avail);
284 copied += avail;
285 length -= avail;
288 m_data->m_writepos = readImpl(m_data->m_buffer, m_data->m_bufferSize);
289 m_data->m_readpos = 0;
290 avail = bufferedLen();
292 if (avail == 0 || m_data->m_nonblocking) {
293 // For nonblocking mode, temporary out of data.
294 break;
298 avail = bufferedLen();
299 if (avail > 0) {
300 int64_t n = length < avail ? length : avail;
301 memcpy(ret + copied, m_data->m_buffer + m_data->m_readpos, n);
302 m_data->m_readpos += n;
303 copied += n;
306 m_data->m_position += copied;
308 assertx(copied <= allocSize);
309 s.shrink(copied);
310 return s;
313 int64_t File::write(const String& data, int64_t length /* = 0 */) {
314 if (seekable()) {
315 int64_t offset = m_data->m_readpos - m_data->m_writepos;
316 // Writing shouldn't change the EOF status, but because we have a
317 // transparent buffer, we need to do read operations on the backing
318 // store, which can.
320 // EOF state isn't just a matter of position on all subclasses;
321 // even seek(0, SEEK_CUR) can change it.
322 auto eof = m_data->m_eof;
323 m_data->m_readpos = m_data->m_writepos = 0; // invalidating read buffer
324 seek(offset, SEEK_CUR);
325 m_data->m_eof = eof;
328 if (length <= 0 || length > data.size()) {
329 length = data.size();
332 if (!length) {
333 return 0;
336 int64_t written = writeImpl(data.data(), length);
337 m_data->m_position += written;
338 return written;
341 int File::putc(char c) {
342 char buf[1];
343 buf[0] = c;
344 int ret = writeImpl(buf, 1);
345 m_data->m_position += ret;
346 return ret;
349 bool File::seek(int64_t offset, int whence /* = SEEK_SET */) {
350 if (whence != SEEK_CUR) {
351 throw_not_supported(__func__, "cannot seek other than SEEK_CUR");
353 if (offset < 0) {
354 throw_not_supported(__func__, "cannot seek backwards");
356 if (offset > 0) {
357 int64_t avail = bufferedLen();
358 assertx(avail >= 0);
359 if (avail >= offset) {
360 m_data->m_readpos += offset;
361 return true;
363 if (avail > 0) {
364 m_data->m_readpos += avail;
365 offset -= avail;
368 while (offset) {
369 char tmp[1024];
370 int64_t nread = offset > (int64_t)sizeof(tmp) ? (int64_t)sizeof(tmp) : offset;
371 nread = readImpl(tmp, nread);
372 if (nread <= 0) {
373 return false;
375 offset -= nread;
378 return true;
381 bool File::setBlocking(bool mode) {
382 int flags = fcntl(fd(), F_GETFL, 0);
383 if (mode) {
384 flags &= ~O_NONBLOCK;
385 } else {
386 flags |= O_NONBLOCK;
388 return fcntl(fd(), F_SETFL, flags) != -1;
391 bool File::setTimeout(uint64_t /*usecs*/) {
392 return false;
395 int64_t File::tell() {
396 throw_not_supported(__func__, "cannot tell");
399 bool File::eof() {
400 throw_not_supported(__func__, "cannot test eof");
403 bool File::rewind() {
404 throw_not_supported(__func__, "cannot rewind");
407 bool File::flush() {
408 return true;
411 bool File::truncate(int64_t /*size*/) {
412 throw_not_supported(__func__, "cannot truncate");
415 bool File::lock(int operation) {
416 bool b = false;
417 return lock(operation, b);
420 bool File::lock(int operation, bool &wouldblock /* = false */) {
421 assertx(m_data->m_fd >= 0);
423 wouldblock = false;
424 if (flock(m_data->m_fd, operation)) {
425 if (errno == EWOULDBLOCK) {
426 wouldblock = true;
428 return false;
430 return true;
433 bool File::stat(struct stat* /*sb*/) {
434 // Undocumented, but Zend returns false for streams where fstat is unsupported
435 return false;
438 const StaticString
439 s_wrapper_type("wrapper_type"),
440 s_stream_type("stream_type"),
441 s_mode("mode"),
442 s_unread_bytes("unread_bytes"),
443 s_seekable("seekable"),
444 s_uri("uri"),
445 s_timed_out("timed_out"),
446 s_blocked("blocked"),
447 s_eof("eof"),
448 s_wrapper_data("wrapper_data");
450 Array File::getMetaData() {
451 return make_darray(
452 s_wrapper_type, getWrapperType(),
453 s_stream_type, getStreamType(),
454 s_mode, String(m_data->m_mode),
455 s_unread_bytes, 0,
456 s_seekable, seekable(),
457 s_uri, String(m_data->m_name),
458 s_timed_out, false,
459 s_blocked, true,
460 s_eof, eof(),
461 s_wrapper_data, getWrapperMetaData()
465 String File::getWrapperType() const {
466 if (!m_wrapperType || m_wrapperType->empty()) {
467 return o_getClassName();
469 return String{m_wrapperType};
472 ///////////////////////////////////////////////////////////////////////////////
473 // utility functions
475 String File::readLine(int64_t maxlen /* = 0 */) {
476 size_t current_buf_size = 0;
477 size_t total_copied = 0;
478 char *ret = nullptr;
479 for (;;) {
480 int64_t avail = bufferedLen();
481 if (avail > 0) {
482 int64_t cpysz = 0;
483 bool done = false;
485 char *readptr = m_data->m_buffer + m_data->m_readpos;
486 const char *eol = nullptr;
487 const char *cr;
488 const char *lf;
489 cr = (const char *)memchr(readptr, '\r', avail);
490 lf = (const char *)memchr(readptr, '\n', avail);
491 if (cr && lf != cr + 1 && !(lf && lf < cr) && cr != &readptr[avail - 1]) {
492 /* mac */
493 eol = cr;
494 } else if ((cr && lf && cr == lf - 1) || (lf)) {
495 /* dos or unix endings */
496 eol = lf;
497 } else if (cr != &readptr[avail - 1]) {
498 eol = cr;
501 if (eol) {
502 cpysz = eol - readptr + 1;
503 done = true;
504 } else {
505 cpysz = avail;
507 if (maxlen > 0 && maxlen <= cpysz) {
508 cpysz = maxlen;
509 done = true;
512 current_buf_size += cpysz + 1;
513 if (ret) {
514 ret = (char *)realloc(ret, current_buf_size);
515 } else {
516 ret = (char *)malloc(current_buf_size);
518 memcpy(ret + total_copied, readptr, cpysz);
520 m_data->m_position += cpysz;
521 m_data->m_readpos += cpysz;
522 maxlen -= cpysz;
523 total_copied += cpysz;
525 if (done) {
526 break;
528 } else if (eof()) {
529 break;
530 } else {
531 if (m_data->m_buffer == nullptr) {
532 m_data->m_buffer = (char *)malloc(m_data->m_chunkSize);
533 m_data->m_bufferSize = m_data->m_chunkSize;
535 m_data->m_writepos = readImpl(m_data->m_buffer, m_data->m_bufferSize);
536 m_data->m_readpos = 0;
537 if (bufferedLen() == 0) {
538 break;
543 if (total_copied == 0) {
544 assertx(ret == nullptr);
545 return String();
548 ret[total_copied] = '\0';
549 return String(ret, total_copied, AttachString);
552 Variant File::readRecord(const String& delimiter, int64_t maxlen /* = 0 */) {
553 if (eof() && m_data->m_writepos == m_data->m_readpos) {
554 return false;
557 if (maxlen <= 0 || maxlen > m_data->m_chunkSize) {
558 maxlen = m_data->m_chunkSize;
561 int64_t avail = bufferedLen();
562 if (m_data->m_buffer == nullptr) {
563 m_data->m_buffer = (char *)malloc(m_data->m_chunkSize * 3);
564 m_data->m_bufferSize = m_data->m_chunkSize * 3;
565 } else if (m_data->m_bufferSize < m_data->m_chunkSize * 3) {
566 auto newbuf = malloc(m_data->m_chunkSize * 3);
567 memcpy(newbuf, m_data->m_buffer, m_data->m_bufferSize);
568 free(m_data->m_buffer);
569 m_data->m_buffer = (char*) newbuf;
570 m_data->m_bufferSize = m_data->m_chunkSize * 3;
573 if (avail < maxlen && !eof()) {
574 assertx(m_data->m_writepos + maxlen - avail <= m_data->m_chunkSize * 3);
575 m_data->m_writepos +=
576 readImpl(m_data->m_buffer + m_data->m_writepos, maxlen - avail);
577 maxlen = bufferedLen();
579 if (m_data->m_readpos >= m_data->m_chunkSize) {
580 memcpy(m_data->m_buffer,
581 m_data->m_buffer + m_data->m_readpos,
582 bufferedLen());
583 m_data->m_writepos -= m_data->m_readpos;
584 m_data->m_readpos = 0;
587 int64_t toread;
588 const char *e;
589 bool skip = false;
590 if (delimiter.empty()) {
591 toread = maxlen;
592 } else {
593 if (delimiter.size() == 1) {
594 e = (const char *)memchr(m_data->m_buffer + m_data->m_readpos,
595 delimiter.charAt(0),
596 bufferedLen());
597 } else {
598 int64_t pos = string_find(m_data->m_buffer + m_data->m_readpos,
599 bufferedLen(),
600 delimiter.data(),
601 delimiter.size(),
603 true);
604 if (pos >= 0) {
605 e = m_data->m_buffer + m_data->m_readpos + pos;
606 } else {
607 e = nullptr;
611 if (!e) {
612 toread = maxlen;
613 } else {
614 toread = e - m_data->m_buffer - m_data->m_readpos;
615 skip = true;
619 if (toread > maxlen && maxlen > 0) {
620 toread = maxlen;
623 if (toread >= 0) {
624 String s = String(toread, ReserveString);
625 char *buf = s.mutableData();
626 if (toread) {
627 memcpy(buf, m_data->m_buffer + m_data->m_readpos, toread);
630 m_data->m_readpos += toread;
631 m_data->m_position += toread;
632 if (skip) {
633 m_data->m_readpos += delimiter.size();
634 m_data->m_position += delimiter.size();
636 s.setSize(toread);
637 return s;
640 return empty_string();
643 int64_t File::print() {
644 int64_t total = 0;
645 while (true) {
646 char buffer[1024];
647 int64_t len = readImpl(buffer, 1024);
648 if (len == 0) break;
649 total += len;
650 g_context->write(buffer, len);
652 return total;
655 int64_t File::printf(const String& format, const Array& args) {
656 String str = string_printf(format.data(), format.size(), args);
657 return write(str);
660 const StaticString s_Unknown("Unknown");
661 const String& File::o_getResourceName() const {
662 if (isInvalid()) return s_Unknown;
663 return s_resource_name;
666 int64_t File::getChunkSize() const{
667 return m_data->m_chunkSize;
670 void File::setChunkSize(int64_t chunk_size) {
672 assertx(chunk_size > 0);
674 m_data->m_chunkSize = chunk_size;
676 if (m_data->m_buffer != nullptr && m_data->m_chunkSize > m_data->m_bufferSize) {
677 m_data->m_buffer = (char *)realloc(m_data->m_buffer, m_data->m_chunkSize);
678 m_data->m_bufferSize = m_data->m_chunkSize;
682 ///////////////////////////////////////////////////////////////////////////////
683 // csv functions
685 int64_t File::writeCSV(const Array& fields, char delimiter_char /* = ',' */,
686 char enclosure_char /* = '"' */,
687 char escape_char /* = '\' */) {
688 int line = 0;
689 int count = fields.size();
690 StringBuffer csvline(1024);
692 for (ArrayIter iter(fields); iter; ++iter) {
693 String value = iter.second().toString();
694 bool need_enclosure = false;
695 for (int i = 0; i < value.size(); i++) {
696 char ch = value.charAt(i);
697 if (ch == delimiter_char || ch == enclosure_char || ch == escape_char ||
698 ch == '\n' || ch == '\r' || ch == '\t' || ch == ' ') {
699 need_enclosure = true;
700 break;
703 if (need_enclosure) {
704 csvline.append(enclosure_char);
705 const char *ch = value.data();
706 const char *end = ch + value.size();
707 bool escaped = false;
708 while (ch < end) {
709 if (*ch == escape_char) {
710 escaped = true;
711 } else if (!escaped && *ch == enclosure_char) {
712 csvline.append(enclosure_char);
713 } else {
714 escaped = false;
716 csvline.append(*ch);
717 ch++;
719 csvline.append(enclosure_char);
720 } else {
721 csvline.append(value);
724 if (++line != count) {
725 csvline.append(delimiter_char);
728 csvline.append('\n');
730 return write(csvline.detach());
733 static const char *lookup_trailing_spaces(const char *ptr, int len) {
734 if (len > 0) {
735 ptr += len;
736 switch (*(ptr - 1)) {
737 case '\n':
738 if (len > 1 && *(ptr - 2) == '\r') {
739 return ptr - 2;
741 /* break is omitted intentionally */
742 case '\r':
743 return ptr - 1;
746 return ptr;
749 Array File::readCSV(int64_t length /* = 0 */,
750 char delimiter_char /* = ',' */,
751 char enclosure_char /* = '"' */,
752 char escape_char /* = '\\' */,
753 const String* input /* = nullptr */) {
754 const String& line = (input != nullptr) ? *input : readLine(length);
755 if (line.empty()) {
756 return null_array;
759 String new_line;
760 const char *buf = line.data();
761 int64_t buf_len = line.size();
763 char *temp, *tptr, *line_end, *limit;
764 const char *bptr;
766 int64_t temp_len, line_end_len;
767 bool first_field = true;
768 int inc_len;
770 /* Now into new section that parses buf for delimiter/enclosure fields */
772 /* Strip trailing space from buf, saving end of line in case required
773 for enclosure field */
774 bptr = buf;
775 tptr = (char *)lookup_trailing_spaces(buf, buf_len);
776 line_end_len = buf_len - (size_t)(tptr - buf);
777 line_end = limit = tptr;
779 /* reserve workspace for building each individual field */
780 temp_len = buf_len;
781 temp = (char *)malloc(temp_len + line_end_len + 1);
783 /* Initialize return array */
784 auto ret = Array::CreateVArray();
786 /* Main loop to read CSV fields */
787 /* NB this routine will return a single null entry for a blank line */
788 do {
789 char *comp_end;
790 const char *hunk_begin;
792 tptr = temp;
794 /* 1. Strip any leading space before an enclosure */
796 inc_len = (bptr < limit);
797 const char *tmp = bptr;
798 while ((*tmp != delimiter_char) && isspace((int)*(unsigned char *)tmp)) {
799 ++tmp;
801 if (*tmp == enclosure_char) {
802 bptr = tmp;
805 if (first_field && bptr == line_end) {
806 ret.append(uninit_variant);
807 break;
809 first_field = false;
811 /* 2. Read field, leaving bptr pointing at start of next field */
812 if (inc_len != 0 && *bptr == enclosure_char) {
813 int state = 0;
815 bptr++; /* move on to first character in field */
816 hunk_begin = bptr;
818 /* 2A. handle enclosure delimited field */
820 for (;;) {
821 switch (inc_len) {
822 case 0:
823 switch (state) {
824 case 2:
825 memcpy(tptr, hunk_begin, bptr - hunk_begin - 1);
826 tptr += (bptr - hunk_begin - 1);
827 hunk_begin = bptr;
828 goto quit_loop_2;
830 case 1:
831 memcpy(tptr, hunk_begin, bptr - hunk_begin);
832 tptr += (bptr - hunk_begin);
833 hunk_begin = bptr;
834 /* break is omitted intentionally */
835 case 0:
837 if (hunk_begin != line_end) {
838 memcpy(tptr, hunk_begin, bptr - hunk_begin);
839 tptr += (bptr - hunk_begin);
840 hunk_begin = bptr;
842 /* add the embedded line end to the field */
843 memcpy(tptr, line_end, line_end_len);
844 tptr += line_end_len;
846 new_line = (input != nullptr) ? String() : readLine(length);
847 const char *new_buf = new_line.data();
848 int64_t new_len = new_line.size();
849 if (new_len == 0) {
850 /* we've got an unterminated enclosure,
851 * assign all the data from the start of
852 * the enclosure to end of data to the
853 * last element */
854 if ((size_t)temp_len > (size_t)(limit - buf)) {
855 goto quit_loop_2;
857 goto out;
859 temp_len += new_len;
860 char *new_temp = (char*)realloc(temp, temp_len);
861 tptr = new_temp + (size_t)(tptr - temp);
862 temp = new_temp;
864 buf_len = new_len;
865 bptr = buf = new_buf;
866 hunk_begin = buf;
868 line_end = limit = (char *)lookup_trailing_spaces(buf, buf_len);
869 line_end_len = buf_len - (size_t)(limit - buf);
870 state = 0;
872 break;
874 break;
875 case 1:
876 /* we need to determine if the enclosure is
877 * 'real' or is it escaped */
878 switch (state) {
879 case 1: /* escaped */
880 bptr++;
881 state = 0;
882 break;
883 case 2: /* embedded enclosure ? let's check it */
884 if (*bptr != enclosure_char) {
885 /* real enclosure */
886 memcpy(tptr, hunk_begin, bptr - hunk_begin - 1);
887 tptr += (bptr - hunk_begin - 1);
888 hunk_begin = bptr;
889 goto quit_loop_2;
891 memcpy(tptr, hunk_begin, bptr - hunk_begin);
892 tptr += (bptr - hunk_begin);
893 bptr++;
894 hunk_begin = bptr;
895 state = 0;
896 break;
897 default:
898 if (*bptr == enclosure_char) {
899 state = 2;
900 } else if (*bptr == escape_char) {
901 state = 1;
903 bptr++;
904 break;
906 break;
908 inc_len = (bptr < limit ? 1 : 0);
911 quit_loop_2:
912 /* look up for a delimiter */
914 for (;;) {
915 switch (inc_len) {
916 case 0:
917 goto quit_loop_3;
919 case 1:
920 if (*bptr == delimiter_char) {
921 goto quit_loop_3;
923 break;
924 default:
925 break;
927 bptr += inc_len;
928 inc_len = (bptr < limit ? 1 : 0);
931 quit_loop_3:
932 memcpy(tptr, hunk_begin, bptr - hunk_begin);
933 tptr += (bptr - hunk_begin);
934 bptr += inc_len;
935 comp_end = tptr;
936 } else {
937 /* 3B. Handle non-enclosure field */
939 hunk_begin = bptr;
941 for (;;) {
942 switch (inc_len) {
943 case 0:
944 goto quit_loop_4;
945 case 1:
946 if (*bptr == delimiter_char) {
947 goto quit_loop_4;
949 break;
950 default:
951 break;
953 bptr += inc_len;
954 inc_len = (bptr < limit ? 1 : 0);
957 quit_loop_4:
958 memcpy(tptr, hunk_begin, bptr - hunk_begin);
959 tptr += (bptr - hunk_begin);
961 comp_end = (char *)lookup_trailing_spaces(temp, tptr - temp);
962 if (*bptr == delimiter_char) {
963 bptr++;
967 /* 3. Now pass our field back to php */
968 *comp_end = '\0';
969 ret.append(String(temp, comp_end - temp, CopyString));
970 } while (inc_len > 0);
971 out:
973 free(temp);
974 return ret;
977 String File::getLastError() {
978 return String(folly::errnoStr(errno));
981 ///////////////////////////////////////////////////////////////////////////////