Пример #1
0
        /// <summary>
        /// Removes a file from <see cref="Files"/>
        /// </summary>
        /// <param name="file">The file to remove</param>
        public void RemoveFile(ArFile file)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (file.Parent != this)
            {
                throw new InvalidOperationException($"Cannot remove this file as it is not part of this {nameof(ArArchiveFile)} instance");
            }

            // If we are removing the SymbolTable
            if (file == SymbolTable)
            {
                SymbolTable = null;
            }

            var i = (int)file.Index;

            _files.RemoveAt(i);
            file.Index = 0;

            // Update indices for other sections
            for (int j = i + 1; j < _files.Count; j++)
            {
                var nextEntry = _files[j];
                nextEntry.Index--;
            }

            file.Parent = null;
        }
Пример #2
0
        /// <summary>
        /// Adds a file to <see cref="Files"/>.
        /// </summary>
        /// <param name="file">A file</param>
        public void AddFile(ArFile file)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (file.Parent != null)
            {
                if (file.Parent == this)
                {
                    throw new InvalidOperationException("Cannot add the file as it is already added");
                }
                if (file.Parent != this)
                {
                    throw new InvalidOperationException($"Cannot add the file as it is already added to another {nameof(ArArchiveFile)} instance");
                }
            }

            if (file is ArSymbolTable symbolTable)
            {
                InsertFileAt(0, file);
                return;
            }

            file.Parent = this;
            file.Index  = (uint)_files.Count;
            _files.Add(file);
        }
Пример #3
0
        /// <summary>
        /// Inserts a file into <see cref="Files"/> at the specified index.
        /// </summary>
        /// <param name="index">Index into <see cref="Files"/> to insert the specified file</param>
        /// <param name="file">The file to insert</param>
        public void InsertFileAt(int index, ArFile file)
        {
            if (index < 0 || index > _files.Count)
            {
                throw new ArgumentOutOfRangeException(nameof(index), $"Invalid index {index}, Must be >= 0 && <= {_files.Count}");
            }
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (file.Parent != null)
            {
                if (file.Parent == this)
                {
                    throw new InvalidOperationException("Cannot add the file as it is already added");
                }
                if (file.Parent != this)
                {
                    throw new InvalidOperationException($"Cannot add the file as it is already added to another {nameof(ArArchiveFile)} instance");
                }
            }

            if (file is ArSymbolTable symbolTable)
            {
                if (SymbolTable == null)
                {
                    if (index != 0)
                    {
                        throw new ArgumentException($"Cannot only add a symbol table at index 0", nameof(file));
                    }
                    SymbolTable = symbolTable;
                }
                else
                {
                    throw new ArgumentException($"Cannot add this symbol table as an existing symbol table is already present in the file entries of {this}", nameof(file));
                }
            }
            else
            {
                if (SymbolTable != null && index == 0)
                {
                    throw new ArgumentException($"Cannot add the entry {file} at index 0 because a symbol table is already set and must be the first entry in the list of files", nameof(file));
                }
            }

            file.Index = (uint)index;
            _files.Insert(index, file);
            file.Parent = this;

            // Update the index of following files
            for (int i = index + 1; i < _files.Count; i++)
            {
                var nextFile = _files[i];
                nextFile.Index++;
            }
        }
        private void WriteFileEntry(Span <byte> buffer, ArFile file)
        {
            Debug.Assert((ulong)(Stream.Position - _startStreamOffset) == file.Offset);
            buffer.Fill((byte)' ');

            var name = file.InternalName;

            bool postFixSlash = false;

            if (name == null)
            {
                name = file.Name;
                if (ArArchiveFile.Kind != ArArchiveKind.Common && !name.EndsWith("/"))
                {
                    postFixSlash = true;
                }
            }

            uint?bsdNameLength = null;

            if (ArArchiveFile.Kind == ArArchiveKind.BSD)
            {
                var nameLength = Encoding.UTF8.GetByteCount(name);
                if (nameLength > ArFile.FieldNameLength)
                {
                    name          = $"#1/{nameLength}";
                    bsdNameLength = (uint)nameLength;
                    postFixSlash  = false;
                }
            }

            // Encode Length
            int length = Encoding.UTF8.GetBytes(name, buffer.Slice(0, ArFile.FieldNameLength));

            if (postFixSlash)
            {
                buffer[length] = (byte)'/';
            }

            if (!(file is ArLongNamesTable))
            {
                // 16	12	File modification timestamp Decimal
                EncodeDecimal(buffer, ArFile.FieldTimestampOffset, ArFile.FieldTimestampLength, (ulong)file.Timestamp.ToUnixTimeSeconds());
                // 28	6	Owner ID    Decimal
                EncodeDecimal(buffer, ArFile.FieldOwnerIdOffset, ArFile.FieldOwnerIdLength, file.OwnerId);
                // 34	6	Group ID    Decimal
                EncodeDecimal(buffer, ArFile.FieldGroupIdOffset, ArFile.FieldGroupIdLength, file.GroupId);
                // 40	8	File mode   Octal
                EncodeOctal(buffer, ArFile.FieldFileModeOffset, ArFile.FieldFileModeLength, file.FileMode);
            }
            // 48	10	File size in bytes Decimal
            EncodeDecimal(buffer, ArFile.FieldFileSizeOffset, ArFile.FieldFileSizeLength, file.Size);

            buffer[ArFile.FieldEndCharactersOffset]     = 0x60;
            buffer[ArFile.FieldEndCharactersOffset + 1] = (byte)'\n';

            // Write the entry
            Stream.Write(buffer);

            // Handle BSD file name by serializing the name before the data if it is required
            if (bsdNameLength.HasValue)
            {
                uint nameLength = bsdNameLength.Value;
                var  bufferName = ArrayPool <byte> .Shared.Rent((int)nameLength);

                Encoding.UTF8.GetBytes(file.Name, 0, file.Name.Length, bufferName, 0);
                try
                {
                    Stream.Write(bufferName, 0, (int)nameLength);
                }
                finally
                {
                    ArrayPool <byte> .Shared.Return(bufferName);
                }
            }

            // Write the content following the entry
            file.WriteInternal(this);

            // Align to even byte
            if ((Stream.Position & 1) != 0)
            {
                Stream.WriteByte((byte)'\n');
            }
        }
        private bool TryReadFileEntry(Span <byte> buffer, out ArFile entry)
        {
            entry = null;

            Debug.Assert((Stream.Position & 1) == 0);

            long entryOffset = Stream.Position;
            int  length      = Stream.Read(buffer);

            if (length == 0)
            {
                return(false);
            }

            if (length < buffer.Length)
            {
                Diagnostics.Error(DiagnosticId.AR_ERR_InvalidFileEntryLength, $"Invalid length {length} while trying to read a file entry from stream at offset {entryOffset}. Expecting {buffer.Length} bytes");
                return(false);
            }
            // 0	16	File identifier ASCII
            // discard right padding characters
            int idLength = 16;

            while (idLength > 0)
            {
                if (buffer[idLength - 1] != ' ')
                {
                    break;
                }
                idLength--;
            }

            string name          = null;
            ulong? bsdNameLength = null;

            if (idLength > 3 && ArArchiveFile.Kind == ArArchiveKind.BSD)
            {
                if (buffer[0] == '#' && buffer[1] == '1' && buffer[2] == '/')
                {
                    // If we have a future header table, we are using it and expecting only numbers
                    if (!TryDecodeDecimal(entryOffset, buffer, 3, ArFile.FieldNameLength - 3, $"BSD Name length following #1/ at offset {entryOffset}", out ulong bsdNameLengthDecoded))
                    {
                        // Don't try to process more entries, the archive might be corrupted
                        return(false);
                    }

                    bsdNameLength = bsdNameLengthDecoded;
                }
            }

            // If the last char is `/`
            // Keep file names with / or //
            // But remove trailing `/`for regular file names
            if (!bsdNameLength.HasValue && ArArchiveFile.Kind != ArArchiveKind.Common && idLength > 0 && buffer[idLength - 1] == '/')
            {
                if (!(idLength == 1 || idLength == 2 && buffer[idLength - 2] == '/'))
                {
                    idLength--;
                }
            }

            if (_futureHeaders != null && buffer[0] == (byte)'/')
            {
                // If we have a future header table, we are using it and expecting only numbers
                if (!TryDecodeDecimal(entryOffset, buffer, 1, ArFile.FieldNameLength - 1, $"Name with offset to Future Headers Table at file offset {entryOffset}", out ulong offsetInFutureHeaders))
                {
                    // Don't try to process more entries, the archive might be corrupted
                    return(false);
                }

                // If the number is ok, check that we have actually a string for this offset
                if (!_futureHeaders.FileNames.TryGetValue((int)offsetInFutureHeaders, out name))
                {
                    Diagnostics.Error(DiagnosticId.AR_ERR_InvalidReferenceToFutureHeadersTable, $"Invalid reference {offsetInFutureHeaders} found at file offset {entryOffset}. This file is invalid.");
                }
            }

            if (!bsdNameLength.HasValue && name == null)
            {
                name = idLength == 0 ? string.Empty : Encoding.UTF8.GetString(buffer.Slice(0, idLength));
            }

            // 16	12	File modification timestamp Decimal
            if (!TryDecodeDecimal(entryOffset, buffer, ArFile.FieldTimestampOffset, ArFile.FieldTimestampLength, "File modification timestamp Decimal", out ulong timestamp))
            {
                return(false);
            }

            // 28	6	Owner ID    Decimal
            if (!TryDecodeDecimal(entryOffset, buffer, ArFile.FieldOwnerIdOffset, ArFile.FieldOwnerIdLength, "Owner ID", out ulong ownerId))
            {
                return(false);
            }

            // 34	6	Group ID    Decimal
            if (!TryDecodeDecimal(entryOffset, buffer, ArFile.FieldGroupIdOffset, ArFile.FieldGroupIdLength, "Group ID", out ulong groupId))
            {
                return(false);
            }

            // 40	8	File mode   Octal
            if (!TryDecodeOctal(entryOffset, buffer, ArFile.FieldFileModeOffset, ArFile.FieldFileModeLength, "File mode", out uint fileMode))
            {
                return(false);
            }

            // 48	10	File size in bytes Decimal
            if (!TryDecodeDecimal(entryOffset, buffer, ArFile.FieldFileSizeOffset, ArFile.FieldFileSizeLength, "File size in bytes", out ulong fileSize))
            {
                return(false);
            }

            // 58	2	Ending characters   0x60 0x0A
            if (buffer[ArFile.FieldEndCharactersOffset] != 0x60 || buffer[ArFile.FieldEndCharactersOffset + 1] != '\n')
            {
                Diagnostics.Error(DiagnosticId.AR_ERR_InvalidCharacterFoundInFileEntry, $"Invalid ASCII characters found 0x{buffer[ArFile.FieldEndCharactersOffset]:x} 0x{buffer[ArFile.FieldEndCharactersOffset+1]:x} instead of `\\n at the end of file entry at offset {entryOffset + ArFile.FieldEndCharactersOffset}");
                return(false);
            }

            entry           = CreateFileEntryFromName(name);
            entry.Timestamp = DateTimeOffset.FromUnixTimeSeconds((long)timestamp);
            entry.OwnerId   = (uint)ownerId;
            entry.GroupId   = (uint)groupId;
            entry.FileMode  = fileMode;
            entry.Offset    = (ulong)entryOffset;
            entry.Size      = fileSize;

            // Read the BSD name if necessary
            if (bsdNameLength.HasValue)
            {
                var nameLength    = (int)bsdNameLength.Value;
                var bufferForName = ArrayPool <byte> .Shared.Rent(nameLength);

                var streamPosition = Stream.Position;
                var dataReadCount  = Stream.Read(bufferForName, 0, nameLength);
                if (dataReadCount != nameLength)
                {
                    Diagnostics.Error(DiagnosticId.AR_ERR_UnexpectedEndOfFile, $"Unexpected end of file while trying to read the filename from the data section of the file entry at offset of {streamPosition}. Expecting {nameLength} bytes while only {dataReadCount} bytes were read from the stream.");
                    return(false);
                }
                name = Encoding.UTF8.GetString(bufferForName, 0, nameLength);
            }

            if (!entry.IsSystem)
            {
                if (name.Contains('/'))
                {
                    Diagnostics.Error(DiagnosticId.AR_ERR_InvalidCharacterInFileEntryName, $"The character `/` was found in the entry `{name}` while it is invalid.");
                    return(false);
                }
                entry.Name = name;
            }

            entry.ReadInternal(this);

            // The end of an entry is always aligned
            if ((Stream.Position & 1) != 0)
            {
                long padOffset = Stream.Position;
                int  pad       = Stream.ReadByte();
                if (pad < 0)
                {
                    Diagnostics.Error(DiagnosticId.AR_ERR_UnexpectedEndOfFile, $"Unexpected end of file while trying to Invalid character 0x{pad:x} found at offset {padOffset} while expecting \\n 0xa");
                    return(false);
                }
                if (pad != '\n')
                {
                    Diagnostics.Error(DiagnosticId.AR_ERR_ExpectingNewLineCharacter, $"Invalid character 0x{pad:x} found at offset {padOffset} while expecting \\n 0xa");
                    return(false);
                }
            }

            return(true);
        }