parent
0785506fad
commit
32d3ceeaf3
@ -1,22 +0,0 @@ |
||||
# Getting Started |
||||
|
||||
### Reference Documentation |
||||
For further reference, please consider the following sections: |
||||
|
||||
* [Official Gradle documentation](https://docs.gradle.org) |
||||
* [Spring Boot Gradle Plugin Reference Guide](https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/gradle-plugin/reference/html/) |
||||
* [Create an OCI image](https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/gradle-plugin/reference/html/#build-image) |
||||
* [Spring Web](https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/reference/htmlsingle/#boot-features-developing-web-applications) |
||||
|
||||
### Guides |
||||
The following guides illustrate how to use some features concretely: |
||||
|
||||
* [Building a RESTful Web Service](https://spring.io/guides/gs/rest-service/) |
||||
* [Serving Web Content with Spring MVC](https://spring.io/guides/gs/serving-web-content/) |
||||
* [Building REST services with Spring](https://spring.io/guides/tutorials/bookmarks/) |
||||
|
||||
### Additional Links |
||||
These additional references should also help you: |
||||
|
||||
* [Gradle Build Scans – insights for your project's build](https://scans.gradle.com#gradle) |
||||
|
@ -1,24 +0,0 @@ |
||||
plugins { |
||||
id 'org.springframework.boot' version '2.3.1.RELEASE' |
||||
id 'io.spring.dependency-management' version '1.0.9.RELEASE' |
||||
id 'java' |
||||
} |
||||
|
||||
group = 'com.example' |
||||
version = '0.0.1-SNAPSHOT' |
||||
sourceCompatibility = '1.8' |
||||
|
||||
repositories { |
||||
mavenCentral() |
||||
} |
||||
|
||||
dependencies { |
||||
implementation 'org.springframework.boot:spring-boot-starter-web' |
||||
testImplementation('org.springframework.boot:spring-boot-starter-test') { |
||||
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine' |
||||
} |
||||
} |
||||
|
||||
test { |
||||
useJUnitPlatform() |
||||
} |
Binary file not shown.
Binary file not shown.
@ -1 +0,0 @@ |
||||
server.port=4503 |
Binary file not shown.
@ -1,5 +0,0 @@ |
||||
distributionBase=GRADLE_USER_HOME |
||||
distributionPath=wrapper/dists |
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-bin.zip |
||||
zipStoreBase=GRADLE_USER_HOME |
||||
zipStorePath=wrapper/dists |
@ -1,185 +0,0 @@ |
||||
#!/usr/bin/env sh |
||||
|
||||
# |
||||
# Copyright 2015 the original author or authors. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# https://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
# |
||||
|
||||
############################################################################## |
||||
## |
||||
## Gradle start up script for UN*X |
||||
## |
||||
############################################################################## |
||||
|
||||
# Attempt to set APP_HOME |
||||
# Resolve links: $0 may be a link |
||||
PRG="$0" |
||||
# Need this for relative symlinks. |
||||
while [ -h "$PRG" ] ; do |
||||
ls=`ls -ld "$PRG"` |
||||
link=`expr "$ls" : '.*-> \(.*\)$'` |
||||
if expr "$link" : '/.*' > /dev/null; then |
||||
PRG="$link" |
||||
else |
||||
PRG=`dirname "$PRG"`"/$link" |
||||
fi |
||||
done |
||||
SAVED="`pwd`" |
||||
cd "`dirname \"$PRG\"`/" >/dev/null |
||||
APP_HOME="`pwd -P`" |
||||
cd "$SAVED" >/dev/null |
||||
|
||||
APP_NAME="Gradle" |
||||
APP_BASE_NAME=`basename "$0"` |
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' |
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value. |
||||
MAX_FD="maximum" |
||||
|
||||
warn () { |
||||
echo "$*" |
||||
} |
||||
|
||||
die () { |
||||
echo |
||||
echo "$*" |
||||
echo |
||||
exit 1 |
||||
} |
||||
|
||||
# OS specific support (must be 'true' or 'false'). |
||||
cygwin=false |
||||
msys=false |
||||
darwin=false |
||||
nonstop=false |
||||
case "`uname`" in |
||||
CYGWIN* ) |
||||
cygwin=true |
||||
;; |
||||
Darwin* ) |
||||
darwin=true |
||||
;; |
||||
MINGW* ) |
||||
msys=true |
||||
;; |
||||
NONSTOP* ) |
||||
nonstop=true |
||||
;; |
||||
esac |
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar |
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM. |
||||
if [ -n "$JAVA_HOME" ] ; then |
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then |
||||
# IBM's JDK on AIX uses strange locations for the executables |
||||
JAVACMD="$JAVA_HOME/jre/sh/java" |
||||
else |
||||
JAVACMD="$JAVA_HOME/bin/java" |
||||
fi |
||||
if [ ! -x "$JAVACMD" ] ; then |
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME |
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the |
||||
location of your Java installation." |
||||
fi |
||||
else |
||||
JAVACMD="java" |
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the |
||||
location of your Java installation." |
||||
fi |
||||
|
||||
# Increase the maximum file descriptors if we can. |
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then |
||||
MAX_FD_LIMIT=`ulimit -H -n` |
||||
if [ $? -eq 0 ] ; then |
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then |
||||
MAX_FD="$MAX_FD_LIMIT" |
||||
fi |
||||
ulimit -n $MAX_FD |
||||
if [ $? -ne 0 ] ; then |
||||
warn "Could not set maximum file descriptor limit: $MAX_FD" |
||||
fi |
||||
else |
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" |
||||
fi |
||||
fi |
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock |
||||
if $darwin; then |
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" |
||||
fi |
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java |
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then |
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"` |
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` |
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"` |
||||
|
||||
# We build the pattern for arguments to be converted via cygpath |
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` |
||||
SEP="" |
||||
for dir in $ROOTDIRSRAW ; do |
||||
ROOTDIRS="$ROOTDIRS$SEP$dir" |
||||
SEP="|" |
||||
done |
||||
OURCYGPATTERN="(^($ROOTDIRS))" |
||||
# Add a user-defined pattern to the cygpath arguments |
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then |
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" |
||||
fi |
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh |
||||
i=0 |
||||
for arg in "$@" ; do |
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` |
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option |
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition |
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` |
||||
else |
||||
eval `echo args$i`="\"$arg\"" |
||||
fi |
||||
i=`expr $i + 1` |
||||
done |
||||
case $i in |
||||
0) set -- ;; |
||||
1) set -- "$args0" ;; |
||||
2) set -- "$args0" "$args1" ;; |
||||
3) set -- "$args0" "$args1" "$args2" ;; |
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;; |
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; |
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; |
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; |
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; |
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; |
||||
esac |
||||
fi |
||||
|
||||
# Escape application args |
||||
save () { |
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done |
||||
echo " " |
||||
} |
||||
APP_ARGS=`save "$@"` |
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules |
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" |
||||
|
||||
exec "$JAVACMD" "$@" |
@ -1,104 +0,0 @@ |
||||
@rem |
||||
@rem Copyright 2015 the original author or authors. |
||||
@rem |
||||
@rem Licensed under the Apache License, Version 2.0 (the "License"); |
||||
@rem you may not use this file except in compliance with the License. |
||||
@rem You may obtain a copy of the License at |
||||
@rem |
||||
@rem https://www.apache.org/licenses/LICENSE-2.0 |
||||
@rem |
||||
@rem Unless required by applicable law or agreed to in writing, software |
||||
@rem distributed under the License is distributed on an "AS IS" BASIS, |
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
@rem See the License for the specific language governing permissions and |
||||
@rem limitations under the License. |
||||
@rem |
||||
|
||||
@if "%DEBUG%" == "" @echo off |
||||
@rem ########################################################################## |
||||
@rem |
||||
@rem Gradle startup script for Windows |
||||
@rem |
||||
@rem ########################################################################## |
||||
|
||||
@rem Set local scope for the variables with windows NT shell |
||||
if "%OS%"=="Windows_NT" setlocal |
||||
|
||||
set DIRNAME=%~dp0 |
||||
if "%DIRNAME%" == "" set DIRNAME=. |
||||
set APP_BASE_NAME=%~n0 |
||||
set APP_HOME=%DIRNAME% |
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter. |
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi |
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. |
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" |
||||
|
||||
@rem Find java.exe |
||||
if defined JAVA_HOME goto findJavaFromJavaHome |
||||
|
||||
set JAVA_EXE=java.exe |
||||
%JAVA_EXE% -version >NUL 2>&1 |
||||
if "%ERRORLEVEL%" == "0" goto init |
||||
|
||||
echo. |
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. |
||||
echo. |
||||
echo Please set the JAVA_HOME variable in your environment to match the |
||||
echo location of your Java installation. |
||||
|
||||
goto fail |
||||
|
||||
:findJavaFromJavaHome |
||||
set JAVA_HOME=%JAVA_HOME:"=% |
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe |
||||
|
||||
if exist "%JAVA_EXE%" goto init |
||||
|
||||
echo. |
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% |
||||
echo. |
||||
echo Please set the JAVA_HOME variable in your environment to match the |
||||
echo location of your Java installation. |
||||
|
||||
goto fail |
||||
|
||||
:init |
||||
@rem Get command-line arguments, handling Windows variants |
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args |
||||
|
||||
:win9xME_args |
||||
@rem Slurp the command line arguments. |
||||
set CMD_LINE_ARGS= |
||||
set _SKIP=2 |
||||
|
||||
:win9xME_args_slurp |
||||
if "x%~1" == "x" goto execute |
||||
|
||||
set CMD_LINE_ARGS=%* |
||||
|
||||
:execute |
||||
@rem Setup the command line |
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar |
||||
|
||||
|
||||
@rem Execute Gradle |
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% |
||||
|
||||
:end |
||||
@rem End local scope for the variables with windows NT shell |
||||
if "%ERRORLEVEL%"=="0" goto mainEnd |
||||
|
||||
:fail |
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of |
||||
rem the _cmd.exe /c_ return code! |
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 |
||||
exit /b 1 |
||||
|
||||
:mainEnd |
||||
if "%OS%"=="Windows_NT" endlocal |
||||
|
||||
:omega |
@ -0,0 +1 @@ |
||||
Subproject commit c23231e281c8d55f275be235158f326244e1c445 |
@ -1 +0,0 @@ |
||||
rootProject.name = 'demo' |
@ -1,19 +0,0 @@ |
||||
package com.example.demo; |
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
import org.springframework.web.bind.annotation.RequestParam; |
||||
import java.util.HashMap; |
||||
|
||||
@RestController |
||||
public class Controller { |
||||
|
||||
@GetMapping("/image") |
||||
public HashMap<String,String> helloWorld(@RequestParam("url") String url){ |
||||
HashMap<String,String> map = new HashMap<>(); |
||||
map.put("test1",Integer.toString(1)); |
||||
map.put("test2","Hello World"); |
||||
return map; |
||||
} |
||||
|
||||
} |
@ -1,14 +0,0 @@ |
||||
package com.example.demo; |
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
|
||||
@RestController |
||||
public class Controller { |
||||
|
||||
@GetMapping("/") |
||||
public String helloWorld() { |
||||
return "Hello from Spring!"; |
||||
} |
||||
|
||||
} |
@ -1,13 +0,0 @@ |
||||
package com.example.demo; |
||||
|
||||
import org.springframework.boot.SpringApplication; |
||||
import org.springframework.boot.autoconfigure.SpringBootApplication; |
||||
|
||||
@SpringBootApplication |
||||
public class DemoApplication { |
||||
|
||||
public static void main(String[] args) { |
||||
SpringApplication.run(DemoApplication.class, args); |
||||
} |
||||
|
||||
} |
@ -1 +0,0 @@ |
||||
server.port=4503 |
@ -1,13 +0,0 @@ |
||||
package com.example.demo; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.springframework.boot.test.context.SpringBootTest; |
||||
|
||||
@SpringBootTest |
||||
class DemoApplicationTests { |
||||
|
||||
@Test |
||||
void contextLoads() { |
||||
} |
||||
|
||||
} |
@ -1 +1,2 @@ |
||||
.env.twitter |
||||
.env.twitter |
||||
files |
@ -0,0 +1,16 @@ |
||||
sudo: false |
||||
language: cpp |
||||
notifications: |
||||
email: false |
||||
env: |
||||
matrix: |
||||
- TRAVIS_NODE_VERSION="4" |
||||
- TRAVIS_NODE_VERSION="6" |
||||
- TRAVIS_NODE_VERSION="8" |
||||
- TRAVIS_NODE_VERSION="10" |
||||
install: |
||||
- rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION |
||||
- node --version |
||||
- npm --version |
||||
- npm install |
||||
script: npm test |
@ -0,0 +1,19 @@ |
||||
Copyright Brian White. All rights reserved. |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to |
||||
deal in the Software without restriction, including without limitation the |
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
||||
sell copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in |
||||
all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
||||
IN THE SOFTWARE. |
@ -0,0 +1,222 @@ |
||||
Description |
||||
=========== |
||||
|
||||
A node.js module for parsing incoming HTML form data. |
||||
|
||||
|
||||
Requirements |
||||
============ |
||||
|
||||
* [node.js](http://nodejs.org/) -- v4.5.0 or newer |
||||
|
||||
|
||||
Install |
||||
======= |
||||
|
||||
npm install busboy |
||||
|
||||
|
||||
Examples |
||||
======== |
||||
|
||||
* Parsing (multipart) with default options: |
||||
|
||||
```javascript |
||||
var http = require('http'), |
||||
inspect = require('util').inspect; |
||||
|
||||
var Busboy = require('busboy'); |
||||
|
||||
http.createServer(function(req, res) { |
||||
if (req.method === 'POST') { |
||||
var busboy = new Busboy({ headers: req.headers }); |
||||
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) { |
||||
console.log('File [' + fieldname + ']: filename: ' + filename + ', encoding: ' + encoding + ', mimetype: ' + mimetype); |
||||
file.on('data', function(data) { |
||||
console.log('File [' + fieldname + '] got ' + data.length + ' bytes'); |
||||
}); |
||||
file.on('end', function() { |
||||
console.log('File [' + fieldname + '] Finished'); |
||||
}); |
||||
}); |
||||
busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) { |
||||
console.log('Field [' + fieldname + ']: value: ' + inspect(val)); |
||||
}); |
||||
busboy.on('finish', function() { |
||||
console.log('Done parsing form!'); |
||||
res.writeHead(303, { Connection: 'close', Location: '/' }); |
||||
res.end(); |
||||
}); |
||||
req.pipe(busboy); |
||||
} else if (req.method === 'GET') { |
||||
res.writeHead(200, { Connection: 'close' }); |
||||
res.end('<html><head></head><body>\ |
||||
<form method="POST" enctype="multipart/form-data">\ |
||||
<input type="text" name="textfield"><br />\ |
||||
<input type="file" name="filefield"><br />\ |
||||
<input type="submit">\ |
||||
</form>\ |
||||
</body></html>'); |
||||
} |
||||
}).listen(8000, function() { |
||||
console.log('Listening for requests'); |
||||
}); |
||||
|
||||
// Example output, using http://nodejs.org/images/ryan-speaker.jpg as the file: |
||||
// |
||||
// Listening for requests |
||||
// File [filefield]: filename: ryan-speaker.jpg, encoding: binary |
||||
// File [filefield] got 11971 bytes |
||||
// Field [textfield]: value: 'testing! :-)' |
||||
// File [filefield] Finished |
||||
// Done parsing form! |
||||
``` |
||||
|
||||
* Save all incoming files to disk: |
||||
|
||||
```javascript |
||||
var http = require('http'), |
||||
path = require('path'), |
||||
os = require('os'), |
||||
fs = require('fs'); |
||||
|
||||
var Busboy = require('busboy'); |
||||
|
||||
http.createServer(function(req, res) { |
||||
if (req.method === 'POST') { |
||||
var busboy = new Busboy({ headers: req.headers }); |
||||
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) { |
||||
var saveTo = path.join(os.tmpDir(), path.basename(fieldname)); |
||||
file.pipe(fs.createWriteStream(saveTo)); |
||||
}); |
||||
busboy.on('finish', function() { |
||||
res.writeHead(200, { 'Connection': 'close' }); |
||||
res.end("That's all folks!"); |
||||
}); |
||||
return req.pipe(busboy); |
||||
} |
||||
res.writeHead(404); |
||||
res.end(); |
||||
}).listen(8000, function() { |
||||
console.log('Listening for requests'); |
||||
}); |
||||
``` |
||||
|
||||
* Parsing (urlencoded) with default options: |
||||
|
||||
```javascript |
||||
var http = require('http'), |
||||
inspect = require('util').inspect; |
||||
|
||||
var Busboy = require('busboy'); |
||||
|
||||
http.createServer(function(req, res) { |
||||
if (req.method === 'POST') { |
||||
var busboy = new Busboy({ headers: req.headers }); |
||||
busboy.on('file', function(fieldname, file, filename, encoding, mimetype) { |
||||
console.log('File [' + fieldname + ']: filename: ' + filename); |
||||
file.on('data', function(data) { |
||||
console.log('File [' + fieldname + '] got ' + data.length + ' bytes'); |
||||
}); |
||||
file.on('end', function() { |
||||
console.log('File [' + fieldname + '] Finished'); |
||||
}); |
||||
}); |
||||
busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) { |
||||
console.log('Field [' + fieldname + ']: value: ' + inspect(val)); |
||||
}); |
||||
busboy.on('finish', function() { |
||||
console.log('Done parsing form!'); |
||||
res.writeHead(303, { Connection: 'close', Location: '/' }); |
||||
res.end(); |
||||
}); |
||||
req.pipe(busboy); |
||||
} else if (req.method === 'GET') { |
||||
res.writeHead(200, { Connection: 'close' }); |
||||
res.end('<html><head></head><body>\ |
||||
<form method="POST">\ |
||||
<input type="text" name="textfield"><br />\ |
||||
<select name="selectfield">\ |
||||
<option value="1">1</option>\ |
||||
<option value="10">10</option>\ |
||||
<option value="100">100</option>\ |
||||
<option value="9001">9001</option>\ |
||||
</select><br />\ |
||||
<input type="checkbox" name="checkfield">Node.js rules!<br />\ |
||||
<input type="submit">\ |
||||
</form>\ |
||||
</body></html>'); |
||||
} |
||||
}).listen(8000, function() { |
||||
console.log('Listening for requests'); |
||||
}); |
||||
|
||||
// Example output: |
||||
// |
||||
// Listening for requests |
||||
// Field [textfield]: value: 'testing! :-)' |
||||
// Field [selectfield]: value: '9001' |
||||
// Field [checkfield]: value: 'on' |
||||
// Done parsing form! |
||||
``` |
||||
|
||||
|
||||
API |
||||
=== |
||||
|
||||
_Busboy_ is a _Writable_ stream |
||||
|
||||
Busboy (special) events |
||||
----------------------- |
||||
|
||||
* **file**(< _string_ >fieldname, < _ReadableStream_ >stream, < _string_ >filename, < _string_ >transferEncoding, < _string_ >mimeType) - Emitted for each new file form field found. `transferEncoding` contains the 'Content-Transfer-Encoding' value for the file stream. `mimeType` contains the 'Content-Type' value for the file stream. |
||||
* Note: if you listen for this event, you should always handle the `stream` no matter if you care about the file contents or not (e.g. you can simply just do `stream.resume();` if you want to discard the contents), otherwise the 'finish' event will never fire on the Busboy instance. However, if you don't care about **any** incoming files, you can simply not listen for the 'file' event at all and any/all files will be automatically and safely discarded (these discarded files do still count towards `files` and `parts` limits). |
||||
* If a configured file size limit was reached, `stream` will both have a boolean property `truncated` (best checked at the end of the stream) and emit a 'limit' event to notify you when this happens. |
||||
|
||||
* **field**(< _string_ >fieldname, < _string_ >value, < _boolean_ >fieldnameTruncated, < _boolean_ >valueTruncated, < _string_ >transferEncoding, < _string_ >mimeType) - Emitted for each new non-file field found. |
||||
|
||||
* **partsLimit**() - Emitted when specified `parts` limit has been reached. No more 'file' or 'field' events will be emitted. |
||||
|
||||
* **filesLimit**() - Emitted when specified `files` limit has been reached. No more 'file' events will be emitted. |
||||
|
||||
* **fieldsLimit**() - Emitted when specified `fields` limit has been reached. No more 'field' events will be emitted. |
||||
|
||||
|
||||
Busboy methods |
||||
-------------- |
||||
|
||||
* **(constructor)**(< _object_ >config) - Creates and returns a new Busboy instance. |
||||
|
||||
* The constructor takes the following valid `config` settings: |
||||
|
||||
* **headers** - _object_ - These are the HTTP headers of the incoming request, which are used by individual parsers. |
||||
|
||||
* **highWaterMark** - _integer_ - highWaterMark to use for this Busboy instance (Default: WritableStream default). |
||||
|
||||
* **fileHwm** - _integer_ - highWaterMark to use for file streams (Default: ReadableStream default). |
||||
|
||||
* **defCharset** - _string_ - Default character set to use when one isn't defined (Default: 'utf8'). |
||||
|
||||
* **preservePath** - _boolean_ - If paths in the multipart 'filename' field shall be preserved. (Default: false). |
||||
|
||||
* **limits** - _object_ - Various limits on incoming data. Valid properties are: |
||||
|
||||
* **fieldNameSize** - _integer_ - Max field name size (in bytes) (Default: 100 bytes). |
||||
|
||||
* **fieldSize** - _integer_ - Max field value size (in bytes) (Default: 1MB). |
||||
|
||||
* **fields** - _integer_ - Max number of non-file fields (Default: Infinity). |
||||
|
||||
* **fileSize** - _integer_ - For multipart forms, the max file size (in bytes) (Default: Infinity). |
||||
|
||||
* **files** - _integer_ - For multipart forms, the max number of file fields (Default: Infinity). |
||||
|
||||
* **parts** - _integer_ - For multipart forms, the max number of parts (fields + files) (Default: Infinity). |
||||
|
||||
* **headerPairs** - _integer_ - For multipart forms, the max number of header key=>value pairs to parse **Default:** 2000 (same as node's http). |
||||
|
||||
* The constructor can throw errors: |
||||
|
||||
* **Unsupported content type: $type** - The `Content-Type` isn't one Busboy can parse. |
||||
|
||||
* **Missing Content-Type** - The provided headers don't include `Content-Type` at all. |
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,88 @@ |
||||
var fs = require('fs'), |
||||
WritableStream = require('stream').Writable, |
||||
inherits = require('util').inherits; |
||||
|
||||
var parseParams = require('./utils').parseParams; |
||||
|
||||
function Busboy(opts) { |
||||
if (!(this instanceof Busboy)) |
||||
return new Busboy(opts); |
||||
if (opts.highWaterMark !== undefined) |
||||
WritableStream.call(this, { highWaterMark: opts.highWaterMark }); |
||||
else |
||||
WritableStream.call(this); |
||||
|
||||
this._done = false; |
||||
this._parser = undefined; |
||||
this._finished = false; |
||||
|
||||
this.opts = opts; |
||||
if (opts.headers && typeof opts.headers['content-type'] === 'string') |
||||
this.parseHeaders(opts.headers); |
||||
else |
||||
throw new Error('Missing Content-Type'); |
||||
} |
||||
inherits(Busboy, WritableStream); |
||||
|
||||
Busboy.prototype.emit = function(ev) { |
||||
if (ev === 'finish') { |
||||
if (!this._done) { |
||||
this._parser && this._parser.end(); |
||||
return; |
||||
} else if (this._finished) { |
||||
return; |
||||
} |
||||
this._finished = true; |
||||
} |
||||
WritableStream.prototype.emit.apply(this, arguments); |
||||
}; |
||||
|
||||
Busboy.prototype.parseHeaders = function(headers) { |
||||
this._parser = undefined; |
||||
if (headers['content-type']) { |
||||
var parsed = parseParams(headers['content-type']), |
||||
matched, type; |
||||
for (var i = 0; i < TYPES.length; ++i) { |
||||
type = TYPES[i]; |
||||
if (typeof type.detect === 'function') |
||||
matched = type.detect(parsed); |
||||
else |
||||
matched = type.detect.test(parsed[0]); |
||||
if (matched) |
||||
break; |
||||
} |
||||
if (matched) { |
||||
var cfg = { |
||||
limits: this.opts.limits, |
||||
headers: headers, |
||||
parsedConType: parsed, |
||||
highWaterMark: undefined, |
||||
fileHwm: undefined, |
||||
defCharset: undefined, |
||||
preservePath: false |
||||
}; |
||||
if (this.opts.highWaterMark) |
||||
cfg.highWaterMark = this.opts.highWaterMark; |
||||
if (this.opts.fileHwm) |
||||
cfg.fileHwm = this.opts.fileHwm; |
||||
cfg.defCharset = this.opts.defCharset; |
||||
cfg.preservePath = this.opts.preservePath; |
||||
this._parser = type(this, cfg); |
||||
return; |
||||
} |
||||
} |
||||
throw new Error('Unsupported content type: ' + headers['content-type']); |
||||
}; |
||||
|
||||
Busboy.prototype._write = function(chunk, encoding, cb) { |
||||
if (!this._parser) |
||||
return cb(new Error('Not ready to parse. Missing Content-Type?')); |
||||
this._parser.write(chunk, cb); |
||||
}; |
||||
|
||||
var TYPES = [ |
||||
require('./types/multipart'), |
||||
require('./types/urlencoded'), |
||||
]; |
||||
|
||||
module.exports = Busboy; |
@ -0,0 +1,325 @@ |
||||
// TODO:
|
||||
// * support 1 nested multipart level
|
||||
// (see second multipart example here:
|
||||
// http://www.w3.org/TR/html401/interact/forms.html#didx-multipartform-data)
|
||||
// * support limits.fieldNameSize
|
||||
// -- this will require modifications to utils.parseParams
|
||||
|
||||
var ReadableStream = require('stream').Readable, |
||||
inherits = require('util').inherits; |
||||
|
||||
var Dicer = require('dicer'); |
||||
|
||||
var parseParams = require('../utils').parseParams, |
||||
decodeText = require('../utils').decodeText, |
||||
basename = require('../utils').basename; |
||||
|
||||
var RE_BOUNDARY = /^boundary$/i, |
||||
RE_FIELD = /^form-data$/i, |
||||
RE_CHARSET = /^charset$/i, |
||||
RE_FILENAME = /^filename$/i, |
||||
RE_NAME = /^name$/i; |
||||
|
||||
Multipart.detect = /^multipart\/form-data/i; |
||||
function Multipart(boy, cfg) { |
||||
if (!(this instanceof Multipart)) |
||||
return new Multipart(boy, cfg); |
||||
var i, |
||||
len, |
||||
self = this, |
||||
boundary, |
||||
limits = cfg.limits, |
||||
parsedConType = cfg.parsedConType || [], |
||||
defCharset = cfg.defCharset || 'utf8', |
||||
preservePath = cfg.preservePath, |
||||
fileopts = (typeof cfg.fileHwm === 'number' |
||||
? { highWaterMark: cfg.fileHwm } |
||||
: {}); |
||||
|
||||
for (i = 0, len = parsedConType.length; i < len; ++i) { |
||||
if (Array.isArray(parsedConType[i]) |
||||
&& RE_BOUNDARY.test(parsedConType[i][0])) { |
||||
boundary = parsedConType[i][1]; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
function checkFinished() { |
||||
if (nends === 0 && finished && !boy._done) { |
||||
finished = false; |
||||
process.nextTick(function() { |
||||
boy._done = true; |
||||
boy.emit('finish'); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
if (typeof boundary !== 'string') |
||||
throw new Error('Multipart: Boundary not found'); |
||||
|
||||
var fieldSizeLimit = (limits && typeof limits.fieldSize === 'number' |
||||
? limits.fieldSize |
||||
: 1 * 1024 * 1024), |
||||
fileSizeLimit = (limits && typeof limits.fileSize === 'number' |
||||
? limits.fileSize |
||||
: Infinity), |
||||
filesLimit = (limits && typeof limits.files === 'number' |
||||
? limits.files |
||||
: Infinity), |
||||
fieldsLimit = (limits && typeof limits.fields === 'number' |
||||
? limits.fields |
||||
: Infinity), |
||||
partsLimit = (limits && typeof limits.parts === 'number' |
||||
? limits.parts |
||||
: Infinity); |
||||
|
||||
var nfiles = 0, |
||||
nfields = 0, |
||||
nends = 0, |
||||
curFile, |
||||
curField, |
||||
finished = false; |
||||
|
||||
this._needDrain = false; |
||||
this._pause = false; |
||||
this._cb = undefined; |
||||
this._nparts = 0; |
||||
this._boy = boy; |
||||
|
||||
var parserCfg = { |
||||
boundary: boundary, |
||||
maxHeaderPairs: (limits && limits.headerPairs) |
||||
}; |
||||
if (fileopts.highWaterMark) |
||||
parserCfg.partHwm = fileopts.highWaterMark; |
||||
if (cfg.highWaterMark) |
||||
parserCfg.highWaterMark = cfg.highWaterMark; |
||||
|
||||
this.parser = new Dicer(parserCfg); |
||||
this.parser.on('drain', function() { |
||||
self._needDrain = false; |
||||
if (self._cb && !self._pause) { |
||||
var cb = self._cb; |
||||
self._cb = undefined; |
||||
cb(); |
||||
} |
||||
}).on('part', function onPart(part) { |
||||
if (++self._nparts > partsLimit) { |
||||
self.parser.removeListener('part', onPart); |
||||
self.parser.on('part', skipPart); |
||||
boy.hitPartsLimit = true; |
||||
boy.emit('partsLimit'); |
||||
return skipPart(part); |
||||
} |
||||
|
||||
// hack because streams2 _always_ doesn't emit 'end' until nextTick, so let
|
||||
// us emit 'end' early since we know the part has ended if we are already
|
||||
// seeing the next part
|
||||
if (curField) { |
||||
var field = curField; |
||||
field.emit('end'); |
||||
field.removeAllListeners('end'); |
||||
} |
||||
|
||||
part.on('header', function(header) { |
||||
var contype, |
||||
fieldname, |
||||
parsed, |
||||
charset, |
||||
encoding, |
||||
filename, |
||||
nsize = 0; |
||||
|
||||
if (header['content-type']) { |
||||
parsed = parseParams(header['content-type'][0]); |
||||
if (parsed[0]) { |
||||
contype = parsed[0].toLowerCase(); |
||||
for (i = 0, len = parsed.length; i < len; ++i) { |
||||
if (RE_CHARSET.test(parsed[i][0])) { |
||||
charset = parsed[i][1].toLowerCase(); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
if (contype === undefined) |
||||
contype = 'text/plain'; |
||||
if (charset === undefined) |
||||
charset = defCharset; |
||||
|
||||
if (header['content-disposition']) { |
||||
parsed = parseParams(header['content-disposition'][0]); |
||||
if (!RE_FIELD.test(parsed[0])) |
||||
return skipPart(part); |
||||
for (i = 0, len = parsed.length; i < len; ++i) { |
||||
if (RE_NAME.test(parsed[i][0])) { |
||||
fieldname = decodeText(parsed[i][1], 'binary', 'utf8'); |
||||
} else if (RE_FILENAME.test(parsed[i][0])) { |
||||
filename = decodeText(parsed[i][1], 'binary', 'utf8'); |
||||
if (!preservePath) |
||||
filename = basename(filename); |
||||
} |
||||
} |
||||
} else |
||||
return skipPart(part); |
||||
|
||||
if (header['content-transfer-encoding']) |
||||
encoding = header['content-transfer-encoding'][0].toLowerCase(); |
||||
else |
||||
encoding = '7bit'; |
||||
|
||||
var onData, |
||||
onEnd; |
||||
if (contype === 'application/octet-stream' || filename !== undefined) { |
||||
// file/binary field
|
||||
if (nfiles === filesLimit) { |
||||
if (!boy.hitFilesLimit) { |
||||
boy.hitFilesLimit = true; |
||||
boy.emit('filesLimit'); |
||||
} |
||||
return skipPart(part); |
||||
} |
||||
|
||||
++nfiles; |
||||
|
||||
if (!boy._events.file) { |
||||
self.parser._ignore(); |
||||
return; |
||||
} |
||||
|
||||
++nends; |
||||
var file = new FileStream(fileopts); |
||||
curFile = file; |
||||
file.on('end', function() { |
||||
--nends; |
||||
self._pause = false; |
||||
checkFinished(); |
||||
if (self._cb && !self._needDrain) { |
||||
var cb = self._cb; |
||||
self._cb = undefined; |
||||
cb(); |
||||
} |
||||
}); |
||||
file._read = function(n) { |
||||
if (!self._pause) |
||||
return; |
||||
self._pause = false; |
||||
if (self._cb && !self._needDrain) { |
||||
var cb = self._cb; |
||||
self._cb = undefined; |
||||
cb(); |
||||
} |
||||
}; |
||||
boy.emit('file', fieldname, file, filename, encoding, contype); |
||||
|
||||
onData = function(data) { |
||||
if ((nsize += data.length) > fileSizeLimit) { |
||||
var extralen = (fileSizeLimit - (nsize - data.length)); |
||||
if (extralen > 0) |
||||
file.push(data.slice(0, extralen)); |
||||
file.emit('limit'); |
||||
file.truncated = true; |
||||
part.removeAllListeners('data'); |
||||
} else if (!file.push(data)) |
||||
self._pause = true; |
||||
}; |
||||
|
||||
onEnd = function() { |
||||
curFile = undefined; |
||||
file.push(null); |
||||
}; |
||||
} else { |
||||
// non-file field
|
||||
if (nfields === fieldsLimit) { |
||||
if (!boy.hitFieldsLimit) { |
||||
boy.hitFieldsLimit = true; |
||||
boy.emit('fieldsLimit'); |
||||
} |
||||
return skipPart(part); |
||||
} |
||||
|
||||
++nfields; |
||||
++nends; |
||||
var buffer = '', |
||||
truncated = false; |
||||
curField = part; |
||||
|
||||
onData = function(data) { |
||||
if ((nsize += data.length) > fieldSizeLimit) { |
||||
var extralen = (fieldSizeLimit - (nsize - data.length)); |
||||
buffer += data.toString('binary', 0, extralen); |
||||
truncated = true; |
||||
part.removeAllListeners('data'); |
||||
} else |
||||
buffer += data.toString('binary'); |
||||
}; |
||||
|
||||
onEnd = function() { |
||||
curField = undefined; |
||||
if (buffer.length) |
||||
buffer = decodeText(buffer, 'binary', charset); |
||||
boy.emit('field', fieldname, buffer, false, truncated, encoding, contype); |
||||
--nends; |
||||
checkFinished(); |
||||
}; |
||||
} |
||||
|
||||
/* As of node@2efe4ab761666 (v0.10.29+/v0.11.14+), busboy had become |
||||
broken. Streams2/streams3 is a huge black box of confusion, but |
||||
somehow overriding the sync state seems to fix things again (and still |
||||
seems to work for previous node versions). |
||||
*/ |
||||
part._readableState.sync = false; |
||||
|
||||
part.on('data', onData); |
||||
part.on('end', onEnd); |
||||
}).on('error', function(err) { |
||||
if (curFile) |
||||
curFile.emit('error', err); |
||||
}); |
||||
}).on('error', function(err) { |
||||
boy.emit('error', err); |
||||
}).on('finish', function() { |
||||
finished = true; |
||||
checkFinished(); |
||||
}); |
||||
} |
||||
|
||||
Multipart.prototype.write = function(chunk, cb) { |
||||
var r; |
||||
if ((r = this.parser.write(chunk)) && !this._pause) |
||||
cb(); |
||||
else { |
||||
this._needDrain = !r; |
||||
this._cb = cb; |
||||
} |
||||
}; |
||||
|
||||
Multipart.prototype.end = function() { |
||||
var self = this; |
||||
if (this._nparts === 0 && !self._boy._done) { |
||||
process.nextTick(function() { |
||||
self._boy._done = true; |
||||
self._boy.emit('finish'); |
||||
}); |
||||
} else if (this.parser.writable) |
||||
this.parser.end(); |
||||
}; |
||||
|
||||
function skipPart(part) { |
||||
part.resume(); |
||||
} |
||||
|
||||
function FileStream(opts) { |
||||
if (!(this instanceof FileStream)) |
||||
return new FileStream(opts); |
||||
ReadableStream.call(this, opts); |
||||
|
||||
this.truncated = false; |
||||
} |
||||
inherits(FileStream, ReadableStream); |
||||
|
||||
FileStream.prototype._read = function(n) {}; |
||||
|
||||
module.exports = Multipart; |
@ -0,0 +1,214 @@ |
||||
var Decoder = require('../utils').Decoder, |
||||
decodeText = require('../utils').decodeText; |
||||
|
||||
var RE_CHARSET = /^charset$/i; |
||||
|
||||
UrlEncoded.detect = /^application\/x-www-form-urlencoded/i; |
||||
function UrlEncoded(boy, cfg) { |
||||
if (!(this instanceof UrlEncoded)) |
||||
return new UrlEncoded(boy, cfg); |
||||
var limits = cfg.limits, |
||||
headers = cfg.headers, |
||||
parsedConType = cfg.parsedConType; |
||||
this.boy = boy; |
||||
|
||||
this.fieldSizeLimit = (limits && typeof limits.fieldSize === 'number' |
||||
? limits.fieldSize |
||||
: 1 * 1024 * 1024); |
||||
this.fieldNameSizeLimit = (limits && typeof limits.fieldNameSize === 'number' |
||||
? limits.fieldNameSize |
||||
: 100); |
||||
this.fieldsLimit = (limits && typeof limits.fields === 'number' |
||||
? limits.fields |
||||
: Infinity); |
||||
|
||||
var charset; |
||||
for (var i = 0, len = parsedConType.length; i < len; ++i) { |
||||
if (Array.isArray(parsedConType[i]) |
||||
&& RE_CHARSET.test(parsedConType[i][0])) { |
||||
charset = parsedConType[i][1].toLowerCase(); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (charset === undefined) |
||||
charset = cfg.defCharset || 'utf8'; |
||||
|
||||
this.decoder = new Decoder(); |
||||
this.charset = charset; |
||||
this._fields = 0; |
||||
this._state = 'key'; |
||||
this._checkingBytes = true; |
||||
this._bytesKey = 0; |
||||
this._bytesVal = 0; |
||||
this._key = ''; |
||||
this._val = ''; |
||||
this._keyTrunc = false; |
||||
this._valTrunc = false; |
||||
this._hitlimit = false; |
||||
} |
||||
|
||||
UrlEncoded.prototype.write = function(data, cb) { |
||||
if (this._fields === this.fieldsLimit) { |
||||
if (!this.boy.hitFieldsLimit) { |
||||
this.boy.hitFieldsLimit = true; |
||||
this.boy.emit('fieldsLimit'); |
||||
} |
||||
return cb(); |
||||
} |
||||
|
||||
var idxeq, idxamp, i, p = 0, len = data.length; |
||||
|
||||
while (p < len) { |
||||
if (this._state === 'key') { |
||||
idxeq = idxamp = undefined; |
||||
for (i = p; i < len; ++i) { |
||||
if (!this._checkingBytes) |
||||
++p; |
||||
if (data[i] === 0x3D/*=*/) { |
||||
idxeq = i; |
||||
break; |
||||
} else if (data[i] === 0x26/*&*/) { |
||||
idxamp = i; |
||||
break; |
||||
} |
||||
if (this._checkingBytes && this._bytesKey === this.fieldNameSizeLimit) { |
||||
this._hitLimit = true; |
||||
break; |
||||
} else if (this._checkingBytes) |
||||
++this._bytesKey; |
||||
} |
||||
|
||||
if (idxeq !== undefined) { |
||||
// key with assignment
|
||||
if (idxeq > p) |
||||
this._key += this.decoder.write(data.toString('binary', p, idxeq)); |
||||
this._state = 'val'; |
||||
|
||||
this._hitLimit = false; |
||||
this._checkingBytes = true; |
||||
this._val = ''; |
||||
this._bytesVal = 0; |
||||
this._valTrunc = false; |
||||
this.decoder.reset(); |
||||
|
||||
p = idxeq + 1; |
||||
} else if (idxamp !== undefined) { |
||||
// key with no assignment
|
||||
++this._fields; |
||||
var key, keyTrunc = this._keyTrunc; |
||||
if (idxamp > p) |
||||
key = (this._key += this.decoder.write(data.toString('binary', p, idxamp))); |
||||
else |
||||
key = this._key; |
||||
|
||||
this._hitLimit = false; |
||||
this._checkingBytes = true; |
||||
this._key = ''; |
||||
this._bytesKey = 0; |
||||
this._keyTrunc = false; |
||||
this.decoder.reset(); |
||||
|
||||
if (key.length) { |
||||
this.boy.emit('field', decodeText(key, 'binary', this.charset), |
||||
'', |
||||
keyTrunc, |
||||
false); |
||||
} |
||||
|
||||
p = idxamp + 1; |
||||
if (this._fields === this.fieldsLimit) |
||||
return cb(); |
||||
} else if (this._hitLimit) { |
||||
// we may not have hit the actual limit if there are encoded bytes...
|
||||
if (i > p) |
||||
this._key += this.decoder.write(data.toString('binary', p, i)); |
||||
p = i; |
||||
if ((this._bytesKey = this._key.length) === this.fieldNameSizeLimit) { |
||||
// yep, we actually did hit the limit
|
||||
this._checkingBytes = false; |
||||
this._keyTrunc = true; |
||||
} |
||||
} else { |
||||
if (p < len) |
||||
this._key += this.decoder.write(data.toString('binary', p)); |
||||
p = len; |
||||
} |
||||
} else { |
||||
idxamp = undefined; |
||||
for (i = p; i < len; ++i) { |
||||
if (!this._checkingBytes) |
||||
++p; |
||||
if (data[i] === 0x26/*&*/) { |
||||
idxamp = i; |
||||
break; |
||||
} |
||||
if (this._checkingBytes && this._bytesVal === this.fieldSizeLimit) { |
||||
this._hitLimit = true; |
||||
break; |
||||
} |
||||
else if (this._checkingBytes) |
||||
++this._bytesVal; |
||||
} |
||||
|
||||
if (idxamp !== undefined) { |
||||
++this._fields; |
||||
if (idxamp > p) |
||||
this._val += this.decoder.write(data.toString('binary', p, idxamp)); |
||||
this.boy.emit('field', decodeText(this._key, 'binary', this.charset), |
||||
decodeText(this._val, 'binary', this.charset), |
||||
this._keyTrunc, |
||||
this._valTrunc); |
||||
this._state = 'key'; |
||||
|
||||
this._hitLimit = false; |
||||
this._checkingBytes = true; |
||||
this._key = ''; |
||||
this._bytesKey = 0; |
||||
this._keyTrunc = false; |
||||
this.decoder.reset(); |
||||
|
||||
p = idxamp + 1; |
||||
if (this._fields === this.fieldsLimit) |
||||
return cb(); |
||||
} else if (this._hitLimit) { |
||||
// we may not have hit the actual limit if there are encoded bytes...
|
||||
if (i > p) |
||||
this._val += this.decoder.write(data.toString('binary', p, i)); |
||||
p = i; |
||||
if ((this._val === '' && this.fieldSizeLimit === 0) |
||||
|| (this._bytesVal = this._val.length) === this.fieldSizeLimit) { |
||||
// yep, we actually did hit the limit
|
||||
this._checkingBytes = false; |
||||
this._valTrunc = true; |
||||
} |
||||
} else { |
||||
if (p < len) |
||||
this._val += this.decoder.write(data.toString('binary', p)); |
||||
p = len; |
||||
} |
||||
} |
||||
} |
||||
cb(); |
||||
}; |
||||
|
||||
UrlEncoded.prototype.end = function() { |
||||
if (this.boy._done) |
||||
return; |
||||
|
||||
if (this._state === 'key' && this._key.length > 0) { |
||||
this.boy.emit('field', decodeText(this._key, 'binary', this.charset), |
||||
'', |
||||
this._keyTrunc, |
||||
false); |
||||
} else if (this._state === 'val') { |
||||
this.boy.emit('field', decodeText(this._key, 'binary', this.charset), |
||||
decodeText(this._val, 'binary', this.charset), |
||||
this._keyTrunc, |
||||
this._valTrunc); |
||||
} |
||||
this.boy._done = true; |
||||
this.boy.emit('finish'); |
||||
}; |
||||
|
||||
module.exports = UrlEncoded; |
@ -0,0 +1,172 @@ |
||||
var jsencoding = require('../deps/encoding/encoding'); |
||||
|
||||
var RE_ENCODED = /%([a-fA-F0-9]{2})/g; |
||||
function encodedReplacer(match, byte) { |
||||
return String.fromCharCode(parseInt(byte, 16)); |
||||
} |
||||
function parseParams(str) { |
||||
var res = [], |
||||
state = 'key', |
||||
charset = '', |
||||
inquote = false, |
||||
escaping = false, |
||||
p = 0, |
||||
tmp = ''; |
||||
|
||||
for (var i = 0, len = str.length; i < len; ++i) { |
||||
if (str[i] === '\\' && inquote) { |
||||
if (escaping) |
||||
escaping = false; |
||||
else { |
||||
escaping = true; |
||||
continue; |
||||
} |
||||
} else if (str[i] === '"') { |
||||
if (!escaping) { |
||||
if (inquote) { |
||||
inquote = false; |
||||
state = 'key'; |
||||
} else |
||||
inquote = true; |
||||
continue; |
||||
} else |
||||
escaping = false; |
||||
} else { |
||||
if (escaping && inquote) |
||||
tmp += '\\'; |
||||
escaping = false; |
||||
if ((state === 'charset' || state === 'lang') && str[i] === "'") { |
||||
if (state === 'charset') { |
||||
state = 'lang'; |
||||
charset = tmp.substring(1); |
||||
} else |
||||
state = 'value'; |
||||
tmp = ''; |
||||
continue; |
||||
} else if (state === 'key' |
||||
&& (str[i] === '*' || str[i] === '=') |
||||
&& res.length) { |
||||
if (str[i] === '*') |
||||
state = 'charset'; |
||||
else |
||||
state = 'value'; |
||||
res[p] = [tmp, undefined]; |
||||
tmp = ''; |
||||
continue; |
||||
} else if (!inquote && str[i] === ';') { |
||||
state = 'key'; |
||||
if (charset) { |
||||
if (tmp.length) { |
||||
tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer), |
||||
'binary', |
||||
charset); |
||||
} |
||||
charset = ''; |
||||
} |
||||
if (res[p] === undefined) |
||||
res[p] = tmp; |
||||
else |
||||
res[p][1] = tmp; |
||||
tmp = ''; |
||||
++p; |
||||
continue; |
||||
} else if (!inquote && (str[i] === ' ' || str[i] === '\t')) |
||||
continue; |
||||
} |
||||
tmp += str[i]; |
||||
} |
||||
if (charset && tmp.length) { |
||||
tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer), |
||||
'binary', |
||||
charset); |
||||
} |
||||
|
||||
if (res[p] === undefined) { |
||||
if (tmp) |
||||
res[p] = tmp; |
||||
} else |
||||
res[p][1] = tmp; |
||||
|
||||
return res; |
||||
}; |
||||
exports.parseParams = parseParams; |
||||
|
||||
|
||||
function decodeText(text, textEncoding, destEncoding) { |
||||
var ret; |
||||
if (text && jsencoding.encodingExists(destEncoding)) { |
||||
try { |
||||
ret = jsencoding.TextDecoder(destEncoding) |
||||
.decode(Buffer.from(text, textEncoding)); |
||||
} catch(e) {} |
||||
} |
||||
return (typeof ret === 'string' ? ret : text); |
||||
} |
||||
exports.decodeText = decodeText; |
||||
|
||||
|
||||
var HEX = [ |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, |
||||
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 |
||||
], RE_PLUS = /\+/g; |
||||
function Decoder() { |
||||
this.buffer = undefined; |
||||
} |
||||
Decoder.prototype.write = function(str) { |
||||
// Replace '+' with ' ' before decoding
|
||||
str = str.replace(RE_PLUS, ' '); |
||||
var res = ''; |
||||
var i = 0, p = 0, len = str.length; |
||||
for (; i < len; ++i) { |
||||
if (this.buffer !== undefined) { |
||||
if (!HEX[str.charCodeAt(i)]) { |
||||
res += '%' + this.buffer; |
||||
this.buffer = undefined; |
||||
--i; // retry character
|
||||
} else { |
||||
this.buffer += str[i]; |
||||
++p; |
||||
if (this.buffer.length === 2) { |
||||
res += String.fromCharCode(parseInt(this.buffer, 16)); |
||||
this.buffer = undefined; |
||||
} |
||||
} |
||||
} else if (str[i] === '%') { |
||||
if (i > p) { |
||||
res += str.substring(p, i); |
||||
p = i; |
||||
} |
||||
this.buffer = ''; |
||||
++p; |
||||
} |
||||
} |
||||
if (p < len && this.buffer === undefined) |
||||
res += str.substring(p); |
||||
return res; |
||||
}; |
||||
Decoder.prototype.reset = function() { |
||||
this.buffer = undefined; |
||||
}; |
||||
exports.Decoder = Decoder; |
||||
|
||||
|
||||
function basename(path) { |
||||
if (typeof path !== 'string') |
||||
return ''; |
||||
for (var i = path.length - 1; i >= 0; --i) { |
||||
switch (path.charCodeAt(i)) { |
||||
case 0x2F: // '/'
|
||||
case 0x5C: // '\'
|
||||
path = path.slice(i + 1); |
||||
return (path === '..' || path === '.' ? '' : path); |
||||
} |
||||
} |
||||
return (path === '..' || path === '.' ? '' : path); |
||||
} |
||||
exports.basename = basename; |
@ -0,0 +1,64 @@ |
||||
{ |
||||
"_from": "busboy@^0.3.1", |
||||
"_id": "busboy@0.3.1", |
||||
"_inBundle": false, |
||||
"_integrity": "sha512-y7tTxhGKXcyBxRKAni+awqx8uqaJKrSFSNFSeRG5CsWNdmy2BIK+6VGWEW7TZnIO/533mtMEA4rOevQV815YJw==", |
||||
"_location": "/busboy", |
||||
"_phantomChildren": {}, |
||||
"_requested": { |
||||
"type": "range", |
||||
"registry": true, |
||||
"raw": "busboy@^0.3.1", |
||||
"name": "busboy", |
||||
"escapedName": "busboy", |
||||
"rawSpec": "^0.3.1", |
||||
"saveSpec": null, |
||||
"fetchSpec": "^0.3.1" |
||||
}, |
||||
"_requiredBy": [ |
||||
"/express-fileupload" |
||||
], |
||||
"_resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz", |
||||
"_shasum": "170899274c5bf38aae27d5c62b71268cd585fd1b", |
||||
"_spec": "busboy@^0.3.1", |
||||
"_where": "/home/sigonasr2/divar/server/node_modules/express-fileupload", |
||||
"author": { |
||||
"name": "Brian White", |
||||
"email": "mscdex@mscdex.net" |
||||
}, |
||||
"bugs": { |
||||
"url": "https://github.com/mscdex/busboy/issues" |
||||
}, |
||||
"bundleDependencies": false, |
||||
"dependencies": { |
||||
"dicer": "0.3.0" |
||||
}, |
||||
"deprecated": false, |
||||
"description": "A streaming parser for HTML form data for node.js", |
||||
"engines": { |
||||
"node": ">=4.5.0" |
||||
}, |
||||
"homepage": "https://github.com/mscdex/busboy#readme", |
||||
"keywords": [ |
||||
"uploads", |
||||
"forms", |
||||
"multipart", |
||||
"form-data" |
||||
], |
||||
"licenses": [ |
||||
{ |
||||
"type": "MIT", |
||||
"url": "http://github.com/mscdex/busboy/raw/master/LICENSE" |
||||
} |
||||
], |
||||
"main": "./lib/main", |
||||
"name": "busboy", |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+ssh://git@github.com/mscdex/busboy.git" |
||||
}, |
||||
"scripts": { |
||||
"test": "node test/test.js" |
||||
}, |
||||
"version": "0.3.1" |
||||
} |
@ -0,0 +1,80 @@ |
||||
var Busboy = require('..'); |
||||
|
||||
var path = require('path'); |
||||
var inspect = require('util').inspect; |
||||
var assert = require('assert'); |
||||
|
||||
function formDataSection(key, value) { |
||||
return Buffer.from('\r\n--' + BOUNDARY |
||||
+ '\r\nContent-Disposition: form-data; name="' |
||||
+ key + '"\r\n\r\n' + value); |
||||
} |
||||
function formDataFile(key, filename, contentType) { |
||||
return Buffer.concat([ |
||||
Buffer.from('\r\n--' + BOUNDARY + '\r\n'), |
||||
Buffer.from('Content-Disposition: form-data; name="' |
||||
+ key + '"; filename="' + filename + '"\r\n'), |
||||
Buffer.from('Content-Type: ' + contentType + '\r\n\r\n'), |
||||
Buffer.allocUnsafe(100000) |
||||
]); |
||||
} |
||||
|
||||
var BOUNDARY = 'u2KxIV5yF1y+xUspOQCCZopaVgeV6Jxihv35XQJmuTx8X3sh'; |
||||
var reqChunks = [ |
||||
Buffer.concat([ |
||||
formDataFile('file', 'file.bin', 'application/octet-stream'), |
||||
formDataSection('foo', 'foo value') |
||||
]), |
||||
formDataSection('bar', 'bar value'), |
||||
Buffer.from('\r\n--' + BOUNDARY + '--\r\n') |
||||
]; |
||||
var busboy = new Busboy({ |
||||
headers: { |
||||
'content-type': 'multipart/form-data; boundary=' + BOUNDARY |
||||
} |
||||
}); |
||||
var finishes = 0; |
||||
var results = []; |
||||
var expected = [ |
||||
['file', 'file', 'file.bin', '7bit', 'application/octet-stream'], |
||||
['field', 'foo', 'foo value', false, false, '7bit', 'text/plain'], |
||||
['field', 'bar', 'bar value', false, false, '7bit', 'text/plain'], |
||||
]; |
||||
|
||||
busboy.on('field', function(key, val, keyTrunc, valTrunc, encoding, contype) { |
||||
results.push(['field', key, val, keyTrunc, valTrunc, encoding, contype]); |
||||
}); |
||||
busboy.on('file', function(fieldname, stream, filename, encoding, mimeType) { |
||||
results.push(['file', fieldname, filename, encoding, mimeType]); |
||||
// Simulate a pipe where the destination is pausing (perhaps due to waiting
|
||||
// for file system write to finish)
|
||||
setTimeout(function() { |
||||
stream.resume(); |
||||
}, 10); |
||||
}); |
||||
busboy.on('finish', function() { |
||||
assert(finishes++ === 0, 'finish emitted multiple times'); |
||||
assert.deepEqual(results.length, |
||||
expected.length, |
||||
'Parsed result count mismatch. Saw ' |
||||
+ results.length |
||||
+ '. Expected: ' + expected.length); |
||||
|
||||
results.forEach(function(result, i) { |
||||
assert.deepEqual(result, |
||||
expected[i], |
||||
'Result mismatch:\nParsed: ' + inspect(result) |
||||
+ '\nExpected: ' + inspect(expected[i])); |
||||
}); |
||||
}).on('error', function(err) { |
||||
assert(false, 'Unexpected error: ' + err.stack); |
||||
}); |
||||
|
||||
reqChunks.forEach(function(buf) { |
||||
busboy.write(buf); |
||||
}); |
||||
busboy.end(); |
||||
|
||||
process.on('exit', function() { |
||||
assert(finishes === 1, 'busboy did not finish'); |
||||
}); |
@ -0,0 +1,343 @@ |
||||
var Busboy = require('..'); |
||||
|
||||
var path = require('path'), |
||||
inspect = require('util').inspect, |
||||
assert = require('assert'); |
||||
|
||||
var EMPTY_FN = function() {}; |
||||
|
||||
var t = 0, |
||||
group = path.basename(__filename, '.js') + '/'; |
||||
var tests = [ |
||||
{ source: [ |
||||
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="file_name_0"', |
||||
'', |
||||
'super alpha file', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="file_name_1"', |
||||
'', |
||||
'super beta file', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', |
||||
'Content-Type: application/octet-stream', |
||||
'', |
||||
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="upload_file_1"; filename="1k_b.dat"', |
||||
'Content-Type: application/octet-stream', |
||||
'', |
||||
'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' |
||||
].join('\r\n') |
||||
], |
||||
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
expected: [ |
||||
['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'], |
||||
['field', 'file_name_1', 'super beta file', false, false, '7bit', 'text/plain'], |
||||
['file', 'upload_file_0', 1023, 0, '1k_a.dat', '7bit', 'application/octet-stream'], |
||||
['file', 'upload_file_1', 1023, 0, '1k_b.dat', '7bit', 'application/octet-stream'] |
||||
], |
||||
what: 'Fields and files' |
||||
}, |
||||
{ source: [ |
||||
['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||
'Content-Disposition: form-data; name="cont"', |
||||
'', |
||||
'some random content', |
||||
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||
'Content-Disposition: form-data; name="pass"', |
||||
'', |
||||
'some random pass', |
||||
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||
'Content-Disposition: form-data; name="bit"', |
||||
'', |
||||
'2', |
||||
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--' |
||||
].join('\r\n') |
||||
], |
||||
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||
expected: [ |
||||
['field', 'cont', 'some random content', false, false, '7bit', 'text/plain'], |
||||
['field', 'pass', 'some random pass', false, false, '7bit', 'text/plain'], |
||||
['field', 'bit', '2', false, false, '7bit', 'text/plain'] |
||||
], |
||||
what: 'Fields only' |
||||
}, |
||||
{ source: [ |
||||
'' |
||||
], |
||||
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||
expected: [], |
||||
what: 'No fields and no files' |
||||
}, |
||||
{ source: [ |
||||
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="file_name_0"', |
||||
'', |
||||
'super alpha file', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', |
||||
'Content-Type: application/octet-stream', |
||||
'', |
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' |
||||
].join('\r\n') |
||||
], |
||||
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
limits: { |
||||
fileSize: 13, |
||||
fieldSize: 5 |
||||
}, |
||||
expected: [ |
||||
['field', 'file_name_0', 'super', false, true, '7bit', 'text/plain'], |
||||
['file', 'upload_file_0', 13, 2, '1k_a.dat', '7bit', 'application/octet-stream'] |
||||
], |
||||
what: 'Fields and files (limits)' |
||||
}, |
||||
{ source: [ |
||||
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="file_name_0"', |
||||
'', |
||||
'super alpha file', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', |
||||
'Content-Type: application/octet-stream', |
||||
'', |
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' |
||||
].join('\r\n') |
||||
], |
||||
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
limits: { |
||||
files: 0 |
||||
}, |
||||
expected: [ |
||||
['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'] |
||||
], |
||||
what: 'Fields and files (limits: 0 files)' |
||||
}, |
||||
{ source: [ |
||||
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="file_name_0"', |
||||
'', |
||||
'super alpha file', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="file_name_1"', |
||||
'', |
||||
'super beta file', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="upload_file_0"; filename="1k_a.dat"', |
||||
'Content-Type: application/octet-stream', |
||||
'', |
||||
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="upload_file_1"; filename="1k_b.dat"', |
||||
'Content-Type: application/octet-stream', |
||||
'', |
||||
'BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' |
||||
].join('\r\n') |
||||
], |
||||
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
expected: [ |
||||
['field', 'file_name_0', 'super alpha file', false, false, '7bit', 'text/plain'], |
||||
['field', 'file_name_1', 'super beta file', false, false, '7bit', 'text/plain'], |
||||
], |
||||
events: ['field'], |
||||
what: 'Fields and (ignored) files' |
||||
}, |
||||
{ source: [ |
||||
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="upload_file_0"; filename="/tmp/1k_a.dat"', |
||||
'Content-Type: application/octet-stream', |
||||
'', |
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="upload_file_1"; filename="C:\\files\\1k_b.dat"', |
||||
'Content-Type: application/octet-stream', |
||||
'', |
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="upload_file_2"; filename="relative/1k_c.dat"', |
||||
'Content-Type: application/octet-stream', |
||||
'', |
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' |
||||
].join('\r\n') |
||||
], |
||||
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
expected: [ |
||||
['file', 'upload_file_0', 26, 0, '1k_a.dat', '7bit', 'application/octet-stream'], |
||||
['file', 'upload_file_1', 26, 0, '1k_b.dat', '7bit', 'application/octet-stream'], |
||||
['file', 'upload_file_2', 26, 0, '1k_c.dat', '7bit', 'application/octet-stream'] |
||||
], |
||||
what: 'Files with filenames containing paths' |
||||
}, |
||||
{ source: [ |
||||
['-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="upload_file_0"; filename="/absolute/1k_a.dat"', |
||||
'Content-Type: application/octet-stream', |
||||
'', |
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="upload_file_1"; filename="C:\\absolute\\1k_b.dat"', |
||||
'Content-Type: application/octet-stream', |
||||
'', |
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
'Content-Disposition: form-data; name="upload_file_2"; filename="relative/1k_c.dat"', |
||||
'Content-Type: application/octet-stream', |
||||
'', |
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZ', |
||||
'-----------------------------paZqsnEHRufoShdX6fh0lUhXBP4k--' |
||||
].join('\r\n') |
||||
], |
||||
boundary: '---------------------------paZqsnEHRufoShdX6fh0lUhXBP4k', |
||||
preservePath: true, |
||||
expected: [ |
||||
['file', 'upload_file_0', 26, 0, '/absolute/1k_a.dat', '7bit', 'application/octet-stream'], |
||||
['file', 'upload_file_1', 26, 0, 'C:\\absolute\\1k_b.dat', '7bit', 'application/octet-stream'], |
||||
['file', 'upload_file_2', 26, 0, 'relative/1k_c.dat', '7bit', 'application/octet-stream'] |
||||
], |
||||
what: 'Paths to be preserved through the preservePath option' |
||||
}, |
||||
{ source: [ |
||||
['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||
'Content-Disposition: form-data; name="cont"', |
||||
'Content-Type: ', |
||||
'', |
||||
'some random content', |
||||
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||
'Content-Disposition: ', |
||||
'', |
||||
'some random pass', |
||||
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--' |
||||
].join('\r\n') |
||||
], |
||||
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||
expected: [ |
||||
['field', 'cont', 'some random content', false, false, '7bit', 'text/plain'] |
||||
], |
||||
what: 'Empty content-type and empty content-disposition' |
||||
}, |
||||
{ source: [ |
||||
['--asdasdasdasd\r\n', |
||||
'Content-Type: text/plain\r\n', |
||||
'Content-Disposition: form-data; name="foo"\r\n', |
||||
'\r\n', |
||||
'asd\r\n', |
||||
'--asdasdasdasd--' |
||||
].join(':)') |
||||
], |
||||
boundary: 'asdasdasdasd', |
||||
expected: [], |
||||
shouldError: 'Unexpected end of multipart data', |
||||
what: 'Stopped mid-header' |
||||
}, |
||||
{ source: [ |
||||
['------WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||
'Content-Disposition: form-data; name="cont"', |
||||
'Content-Type: application/json', |
||||
'', |
||||
'{}', |
||||
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--', |
||||
].join('\r\n') |
||||
], |
||||
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||
expected: [ |
||||
['field', 'cont', '{}', false, false, '7bit', 'application/json'] |
||||
], |
||||
what: 'content-type for fields' |
||||
}, |
||||
{ source: [ |
||||
'------WebKitFormBoundaryTB2MiQ36fnSJlrhY--\r\n' |
||||
], |
||||
boundary: '----WebKitFormBoundaryTB2MiQ36fnSJlrhY', |
||||
expected: [], |
||||
what: 'empty form' |
||||
} |
||||
]; |
||||
|
||||
function next() { |
||||
if (t === tests.length) |
||||
return; |
||||
|
||||
var v = tests[t]; |
||||
|
||||
var busboy = new Busboy({ |
||||
limits: v.limits, |
||||
preservePath: v.preservePath, |
||||
headers: { |
||||
'content-type': 'multipart/form-data; boundary=' + v.boundary |
||||
} |
||||
}), |
||||
finishes = 0, |
||||
results = []; |
||||
|
||||
if (v.events === undefined || v.events.indexOf('field') > -1) { |
||||
busboy.on('field', function(key, val, keyTrunc, valTrunc, encoding, contype) { |
||||
results.push(['field', key, val, keyTrunc, valTrunc, encoding, contype]); |
||||
}); |
||||
} |
||||
if (v.events === undefined || v.events.indexOf('file') > -1) { |
||||
busboy.on('file', function(fieldname, stream, filename, encoding, mimeType) { |
||||
var nb = 0, |
||||
info = ['file', |
||||
fieldname, |
||||
nb, |
||||
0, |
||||
filename, |
||||
encoding, |
||||
mimeType]; |
||||
results.push(info); |
||||
stream.on('data', function(d) { |
||||
nb += d.length; |
||||
}).on('limit', function() { |
||||
++info[3]; |
||||
}).on('end', function() { |
||||
info[2] = nb; |
||||
if (stream.truncated) |
||||
++info[3]; |
||||
}); |
||||
}); |
||||
} |
||||
busboy.on('finish', function() { |
||||
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times')); |
||||
assert.deepEqual(results.length, |
||||
v.expected.length, |
||||
makeMsg(v.what, 'Parsed result count mismatch. Saw ' |
||||
+ results.length |
||||
+ '. Expected: ' + v.expected.length)); |
||||
|
||||
results.forEach(function(result, i) { |
||||
assert.deepEqual(result, |
||||
v.expected[i], |
||||
makeMsg(v.what, |
||||
'Result mismatch:\nParsed: ' + inspect(result) |
||||
+ '\nExpected: ' + inspect(v.expected[i])) |
||||
); |
||||
}); |
||||
++t; |
||||
next(); |
||||
}).on('error', function(err) { |
||||
if (!v.shouldError || v.shouldError !== err.message) |
||||
assert(false, makeMsg(v.what, 'Unexpected error: ' + err)); |
||||
}); |
||||
|
||||
v.source.forEach(function(s) { |
||||
busboy.write(Buffer.from(s, 'utf8'), EMPTY_FN); |
||||
}); |
||||
busboy.end(); |
||||
} |
||||
next(); |
||||
|
||||
function makeMsg(what, msg) { |
||||
return '[' + group + what + ']: ' + msg; |
||||
} |
||||
|
||||
process.on('exit', function() { |
||||
assert(t === tests.length, |
||||
makeMsg('_exit', |
||||
'Only finished ' + t + '/' + tests.length + ' tests')); |
||||
}); |
@ -0,0 +1,183 @@ |
||||
var Busboy = require('..'); |
||||
|
||||
var path = require('path'), |
||||
inspect = require('util').inspect, |
||||
assert = require('assert'); |
||||
|
||||
var EMPTY_FN = function() {}; |
||||
|
||||
var t = 0, |
||||
group = path.basename(__filename, '.js') + '/'; |
||||
|
||||
var tests = [ |
||||
{ source: ['foo'], |
||||
expected: [['foo', '', false, false]], |
||||
what: 'Unassigned value' |
||||
}, |
||||
{ source: ['foo=bar'], |
||||
expected: [['foo', 'bar', false, false]], |
||||
what: 'Assigned value' |
||||
}, |
||||
{ source: ['foo&bar=baz'], |
||||
expected: [['foo', '', false, false], |
||||
['bar', 'baz', false, false]], |
||||
what: 'Unassigned and assigned value' |
||||
}, |
||||
{ source: ['foo=bar&baz'], |
||||
expected: [['foo', 'bar', false, false], |
||||
['baz', '', false, false]], |
||||
what: 'Assigned and unassigned value' |
||||
}, |
||||
{ source: ['foo=bar&baz=bla'], |
||||
expected: [['foo', 'bar', false, false], |
||||
['baz', 'bla', false, false]], |
||||
what: 'Two assigned values' |
||||
}, |
||||
{ source: ['foo&bar'], |
||||
expected: [['foo', '', false, false], |
||||
['bar', '', false, false]], |
||||
what: 'Two unassigned values' |
||||
}, |
||||
{ source: ['foo&bar&'], |
||||
expected: [['foo', '', false, false], |
||||
['bar', '', false, false]], |
||||
what: 'Two unassigned values and ampersand' |
||||
}, |
||||
{ source: ['foo=bar+baz%2Bquux'], |
||||
expected: [['foo', 'bar baz+quux', false, false]], |
||||
what: 'Assigned value with (plus) space' |
||||
}, |
||||
{ source: ['foo=bar%20baz%21'], |
||||
expected: [['foo', 'bar baz!', false, false]], |
||||
what: 'Assigned value with encoded bytes' |
||||
}, |
||||
{ source: ['foo%20bar=baz%20bla%21'], |
||||
expected: [['foo bar', 'baz bla!', false, false]], |
||||
what: 'Assigned value with encoded bytes #2' |
||||
}, |
||||
{ source: ['foo=bar%20baz%21&num=1000'], |
||||
expected: [['foo', 'bar baz!', false, false], |
||||
['num', '1000', false, false]], |
||||
what: 'Two assigned values, one with encoded bytes' |
||||
}, |
||||
{ source: ['foo=bar&baz=bla'], |
||||
expected: [], |
||||
what: 'Limits: zero fields', |
||||
limits: { fields: 0 } |
||||
}, |
||||
{ source: ['foo=bar&baz=bla'], |
||||
expected: [['foo', 'bar', false, false]], |
||||
what: 'Limits: one field', |
||||
limits: { fields: 1 } |
||||
}, |
||||
{ source: ['foo=bar&baz=bla'], |
||||
expected: [['foo', 'bar', false, false], |
||||
['baz', 'bla', false, false]], |
||||
what: 'Limits: field part lengths match limits', |
||||
limits: { fieldNameSize: 3, fieldSize: 3 } |
||||
}, |
||||
{ source: ['foo=bar&baz=bla'], |
||||
expected: [['fo', 'bar', true, false], |
||||
['ba', 'bla', true, false]], |
||||
what: 'Limits: truncated field name', |
||||
limits: { fieldNameSize: 2 } |
||||
}, |
||||
{ source: ['foo=bar&baz=bla'], |
||||
expected: [['foo', 'ba', false, true], |
||||
['baz', 'bl', false, true]], |
||||
what: 'Limits: truncated field value', |
||||
limits: { fieldSize: 2 } |
||||
}, |
||||
{ source: ['foo=bar&baz=bla'], |
||||
expected: [['fo', 'ba', true, true], |
||||
['ba', 'bl', true, true]], |
||||
what: 'Limits: truncated field name and value', |
||||
limits: { fieldNameSize: 2, fieldSize: 2 } |
||||
}, |
||||
{ source: ['foo=bar&baz=bla'], |
||||
expected: [['fo', '', true, true], |
||||
['ba', '', true, true]], |
||||
what: 'Limits: truncated field name and zero value limit', |
||||
limits: { fieldNameSize: 2, fieldSize: 0 } |
||||
}, |
||||
{ source: ['foo=bar&baz=bla'], |
||||
expected: [['', '', true, true], |
||||
['', '', true, true]], |
||||
what: 'Limits: truncated zero field name and zero value limit', |
||||
limits: { fieldNameSize: 0, fieldSize: 0 } |
||||
}, |
||||
{ source: ['&'], |
||||
expected: [], |
||||
what: 'Ampersand' |
||||
}, |
||||
{ source: ['&&&&&'], |
||||
expected: [], |
||||
what: 'Many ampersands' |
||||
}, |
||||
{ source: ['='], |
||||
expected: [['', '', false, false]], |
||||
what: 'Assigned value, empty name and value' |
||||
}, |
||||
{ source: [''], |
||||
expected: [], |
||||
what: 'Nothing' |
||||
}, |
||||
]; |
||||
|
||||
function next() { |
||||
if (t === tests.length) |
||||
return; |
||||
|
||||
var v = tests[t]; |
||||
|
||||
var busboy = new Busboy({ |
||||
limits: v.limits, |
||||
headers: { |
||||
'content-type': 'application/x-www-form-urlencoded; charset=utf-8' |
||||
} |
||||
}), |
||||
finishes = 0, |
||||
results = []; |
||||
|
||||
busboy.on('field', function(key, val, keyTrunc, valTrunc) { |
||||
results.push([key, val, keyTrunc, valTrunc]); |
||||
}); |
||||
busboy.on('file', function() { |
||||
throw new Error(makeMsg(v.what, 'Unexpected file')); |
||||
}); |
||||
busboy.on('finish', function() { |
||||
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times')); |
||||
assert.deepEqual(results.length, |
||||
v.expected.length, |
||||
makeMsg(v.what, 'Parsed result count mismatch. Saw ' |
||||
+ results.length |
||||
+ '. Expected: ' + v.expected.length)); |
||||
|
||||
var i = 0; |
||||
results.forEach(function(result) { |
||||
assert.deepEqual(result, |
||||
v.expected[i], |
||||
makeMsg(v.what, |
||||
'Result mismatch:\nParsed: ' + inspect(result) |
||||
+ '\nExpected: ' + inspect(v.expected[i])) |
||||
); |
||||
++i; |
||||
}); |
||||
++t; |
||||
next(); |
||||
}); |
||||
|
||||
v.source.forEach(function(s) { |
||||
busboy.write(Buffer.from(s, 'utf8'), EMPTY_FN); |
||||
}); |
||||
busboy.end(); |
||||
} |
||||
next(); |
||||
|
||||
function makeMsg(what, msg) { |
||||
return '[' + group + what + ']: ' + msg; |
||||
} |
||||
|
||||
process.on('exit', function() { |
||||
assert(t === tests.length, makeMsg('_exit', 'Only finished ' + t + '/' + tests.length + ' tests')); |
||||
}); |
@ -0,0 +1,66 @@ |
||||
var Decoder = require('../lib/utils').Decoder; |
||||
|
||||
var path = require('path'), |
||||
assert = require('assert'); |
||||
|
||||
var group = path.basename(__filename, '.js') + '/'; |
||||
|
||||
[ |
||||
{ source: ['Hello world'], |
||||
expected: 'Hello world', |
||||
what: 'No encoded bytes' |
||||
}, |
||||
{ source: ['Hello%20world'], |
||||
expected: 'Hello world', |
||||
what: 'One full encoded byte' |
||||
}, |
||||
{ source: ['Hello%20world%21'], |
||||
expected: 'Hello world!', |
||||
what: 'Two full encoded bytes' |
||||
}, |
||||
{ source: ['Hello%', '20world'], |
||||
expected: 'Hello world', |
||||
what: 'One full encoded byte split #1' |
||||
}, |
||||
{ source: ['Hello%2', '0world'], |
||||
expected: 'Hello world', |
||||
what: 'One full encoded byte split #2' |
||||
}, |
||||
{ source: ['Hello%20', 'world'], |
||||
expected: 'Hello world', |
||||
what: 'One full encoded byte (concat)' |
||||
}, |
||||
{ source: ['Hello%2Qworld'], |
||||
expected: 'Hello%2Qworld', |
||||
what: 'Malformed encoded byte #1' |
||||
}, |
||||
{ source: ['Hello%world'], |
||||
expected: 'Hello%world', |
||||
what: 'Malformed encoded byte #2' |
||||
}, |
||||
{ source: ['Hello+world'], |
||||
expected: 'Hello world', |
||||
what: 'Plus to space' |
||||
}, |
||||
{ source: ['Hello+world%21'], |
||||
expected: 'Hello world!', |
||||
what: 'Plus and encoded byte' |
||||
}, |
||||
{ source: ['5%2B5%3D10'], |
||||
expected: '5+5=10', |
||||
what: 'Encoded plus' |
||||
}, |
||||
{ source: ['5+%2B+5+%3D+10'], |
||||
expected: '5 + 5 = 10', |
||||
what: 'Spaces and encoded plus' |
||||
}, |
||||
].forEach(function(v) { |
||||
var dec = new Decoder(), result = ''; |
||||
v.source.forEach(function(s) { |
||||
result += dec.write(s); |
||||
}); |
||||
var msg = '[' + group + v.what + ']: decoded string mismatch.\n' |
||||
+ 'Saw: ' + result + '\n' |
||||
+ 'Expected: ' + v.expected; |
||||
assert.deepEqual(result, v.expected, msg); |
||||
}); |
@ -0,0 +1,96 @@ |
||||
var parseParams = require('../lib/utils').parseParams; |
||||
|
||||
var path = require('path'), |
||||
assert = require('assert'), |
||||
inspect = require('util').inspect; |
||||
|
||||
var group = path.basename(__filename, '.js') + '/'; |
||||
|
||||
[ |
||||
{ source: 'video/ogg', |
||||
expected: ['video/ogg'], |
||||
what: 'No parameters' |
||||
}, |
||||
{ source: 'video/ogg;', |
||||
expected: ['video/ogg'], |
||||
what: 'No parameters (with separator)' |
||||
}, |
||||
{ source: 'video/ogg; ', |
||||
expected: ['video/ogg'], |
||||
what: 'No parameters (with separator followed by whitespace)' |
||||
}, |
||||
{ source: ';video/ogg', |
||||
expected: ['', 'video/ogg'], |
||||
what: 'Empty parameter' |
||||
}, |
||||
{ source: 'video/*', |
||||
expected: ['video/*'], |
||||
what: 'Subtype with asterisk' |
||||
}, |
||||
{ source: 'text/plain; encoding=utf8', |
||||
expected: ['text/plain', ['encoding', 'utf8']], |
||||
what: 'Unquoted' |
||||
}, |
||||
{ source: 'text/plain; encoding=', |
||||
expected: ['text/plain', ['encoding', '']], |
||||
what: 'Unquoted empty string' |
||||
}, |
||||
{ source: 'text/plain; encoding="utf8"', |
||||
expected: ['text/plain', ['encoding', 'utf8']], |
||||
what: 'Quoted' |
||||
}, |
||||
{ source: 'text/plain; greeting="hello \\"world\\""', |
||||
expected: ['text/plain', ['greeting', 'hello "world"']], |
||||
what: 'Quotes within quoted' |
||||
}, |
||||
{ source: 'text/plain; encoding=""', |
||||
expected: ['text/plain', ['encoding', '']], |
||||
what: 'Quoted empty string' |
||||
}, |
||||
{ source: 'text/plain; encoding="utf8";\t foo=bar;test', |
||||
expected: ['text/plain', ['encoding', 'utf8'], ['foo', 'bar'], 'test'], |
||||
what: 'Multiple params with various spacing' |
||||
}, |
||||
{ source: "text/plain; filename*=iso-8859-1'en'%A3%20rates", |
||||
expected: ['text/plain', ['filename', '£ rates']], |
||||
what: 'Extended parameter (RFC 5987) with language' |
||||
}, |
||||
{ source: "text/plain; filename*=utf-8''%c2%a3%20and%20%e2%82%ac%20rates", |
||||
expected: ['text/plain', ['filename', '£ and € rates']], |
||||
what: 'Extended parameter (RFC 5987) without language' |
||||
}, |
||||
{ source: "text/plain; filename*=utf-8''%E6%B5%8B%E8%AF%95%E6%96%87%E6%A1%A3", |
||||
expected: ['text/plain', ['filename', '测试文档']], |
||||
what: 'Extended parameter (RFC 5987) without language #2' |
||||
}, |
||||
{ source: "text/plain; filename*=iso-8859-1'en'%A3%20rates; altfilename*=utf-8''%c2%a3%20and%20%e2%82%ac%20rates", |
||||
expected: ['text/plain', ['filename', '£ rates'], ['altfilename', '£ and € rates']], |
||||
what: 'Multiple extended parameters (RFC 5987) with mixed charsets' |
||||
}, |
||||
{ source: "text/plain; filename*=iso-8859-1'en'%A3%20rates; altfilename=\"foobarbaz\"", |
||||
expected: ['text/plain', ['filename', '£ rates'], ['altfilename', 'foobarbaz']], |
||||
what: 'Mixed regular and extended parameters (RFC 5987)' |
||||
}, |
||||
{ source: "text/plain; filename=\"foobarbaz\"; altfilename*=iso-8859-1'en'%A3%20rates", |
||||
expected: ['text/plain', ['filename', 'foobarbaz'], ['altfilename', '£ rates']], |
||||
what: 'Mixed regular and extended parameters (RFC 5987) #2' |
||||
}, |
||||
{ source: 'text/plain; filename="C:\\folder\\test.png"', |
||||
expected: ['text/plain', ['filename', 'C:\\folder\\test.png']], |
||||
what: 'Unescaped backslashes should be considered backslashes' |
||||
}, |
||||
{ source: 'text/plain; filename="John \\"Magic\\" Smith.png"', |
||||
expected: ['text/plain', ['filename', 'John "Magic" Smith.png']], |
||||
what: 'Escaped double-quotes should be considered double-quotes' |
||||
}, |
||||
{ source: 'multipart/form-data; charset=utf-8; boundary=0xKhTmLbOuNdArY', |
||||
expected: ['multipart/form-data', ['charset', 'utf-8'], ['boundary', '0xKhTmLbOuNdArY']], |
||||
what: 'Multiple non-quoted parameters' |
||||
}, |
||||
].forEach(function(v) { |
||||
var result = parseParams(v.source), |
||||
msg = '[' + group + v.what + ']: parsed parameters mismatch.\n' |
||||
+ 'Saw: ' + inspect(result) + '\n' |
||||
+ 'Expected: ' + inspect(v.expected); |
||||
assert.deepEqual(result, v.expected, msg); |
||||
}); |
@ -0,0 +1,4 @@ |
||||
require('fs').readdirSync(__dirname).forEach(function(f) { |
||||
if (f.substr(0, 5) === 'test-') |
||||
require('./' + f); |
||||
}); |
@ -0,0 +1,16 @@ |
||||
sudo: false |
||||
language: cpp |
||||
notifications: |
||||
email: false |
||||
env: |
||||
matrix: |
||||
- TRAVIS_NODE_VERSION="4" |
||||
- TRAVIS_NODE_VERSION="6" |
||||
- TRAVIS_NODE_VERSION="8" |
||||
- TRAVIS_NODE_VERSION="10" |
||||
install: |
||||
- rm -rf ~/.nvm && git clone https://github.com/creationix/nvm.git ~/.nvm && source ~/.nvm/nvm.sh && nvm install $TRAVIS_NODE_VERSION |
||||
- node --version |
||||
- npm --version |
||||
- npm install |
||||
script: npm test |
@ -0,0 +1,19 @@ |
||||
Copyright Brian White. All rights reserved. |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to |
||||
deal in the Software without restriction, including without limitation the |
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
||||
sell copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in |
||||
all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
||||
IN THE SOFTWARE. |
@ -0,0 +1,122 @@ |
||||
|
||||
Description |
||||
=========== |
||||
|
||||
A very fast streaming multipart parser for node.js. |
||||
|
||||
Benchmarks can be found [here](https://github.com/mscdex/dicer/wiki/Benchmarks). |
||||
|
||||
|
||||
Requirements |
||||
============ |
||||
|
||||
* [node.js](http://nodejs.org/) -- v4.5.0 or newer |
||||
|
||||
|
||||
Install |
||||
============ |
||||
|
||||
npm install dicer |
||||
|
||||
|
||||
Examples |
||||
======== |
||||
|
||||
* Parse an HTTP form upload |
||||
|
||||
```javascript |
||||
var inspect = require('util').inspect, |
||||
http = require('http'); |
||||
|
||||
var Dicer = require('dicer'); |
||||
|
||||
// quick and dirty way to parse multipart boundary |
||||
var RE_BOUNDARY = /^multipart\/.+?(?:; boundary=(?:(?:"(.+)")|(?:([^\s]+))))$/i, |
||||
HTML = Buffer.from('<html><head></head><body>\ |
||||
<form method="POST" enctype="multipart/form-data">\ |
||||
<input type="text" name="textfield"><br />\ |
||||
<input type="file" name="filefield"><br />\ |
||||
<input type="submit">\ |
||||
</form>\ |
||||
</body></html>'), |
||||
PORT = 8080; |
||||
|
||||
http.createServer(function(req, res) { |
||||
var m; |
||||
if (req.method === 'POST' |
||||
&& req.headers['content-type'] |
||||
&& (m = RE_BOUNDARY.exec(req.headers['content-type']))) { |
||||
var d = new Dicer({ boundary: m[1] || m[2] }); |
||||
|
||||
d.on('part', function(p) { |
||||
console.log('New part!'); |
||||
p.on('header', function(header) { |
||||
for (var h in header) { |
||||
console.log('Part header: k: ' + inspect(h) |
||||
+ ', v: ' + inspect(header[h])); |
||||
} |
||||
}); |
||||
p.on('data', function(data) { |
||||
console.log('Part data: ' + inspect(data.toString())); |
||||
}); |
||||
p.on('end', function() { |
||||
console.log('End of part\n'); |
||||
}); |
||||
}); |
||||
d.on('finish', function() { |
||||
console.log('End of parts'); |
||||
res.writeHead(200); |
||||
res.end('Form submission successful!'); |
||||
}); |
||||
req.pipe(d); |
||||
} else if (req.method === 'GET' && req.url === '/') { |
||||
res.writeHead(200); |
||||
res.end(HTML); |
||||
} else { |
||||
res.writeHead(404); |
||||
res.end(); |
||||
} |
||||
}).listen(PORT, function() { |
||||
console.log('Listening for requests on port ' + PORT); |
||||
}); |
||||
``` |
||||
|
||||
|
||||
API |
||||
=== |
||||
|
||||
_Dicer_ is a _WritableStream_ |
||||
|
||||
Dicer (special) events |
||||
---------------------- |
||||
|
||||
* **finish**() - Emitted when all parts have been parsed and the Dicer instance has been ended. |
||||
|
||||
* **part**(< _PartStream_ >stream) - Emitted when a new part has been found. |
||||
|
||||
* **preamble**(< _PartStream_ >stream) - Emitted for preamble if you should happen to need it (can usually be ignored). |
||||
|
||||
* **trailer**(< _Buffer_ >data) - Emitted when trailing data was found after the terminating boundary (as with the preamble, this can usually be ignored too). |
||||
|
||||
|
||||
Dicer methods |
||||
------------- |
||||
|
||||
* **(constructor)**(< _object_ >config) - Creates and returns a new Dicer instance with the following valid `config` settings: |
||||
|
||||
* **boundary** - _string_ - This is the boundary used to detect the beginning of a new part. |
||||
|
||||
* **headerFirst** - _boolean_ - If true, preamble header parsing will be performed first. |
||||
|
||||
* **maxHeaderPairs** - _integer_ - The maximum number of header key=>value pairs to parse **Default:** 2000 (same as node's http). |
||||
|
||||
* **setBoundary**(< _string_ >boundary) - _(void)_ - Sets the boundary to use for parsing and performs some initialization needed for parsing. You should only need to use this if you set `headerFirst` to true in the constructor and are parsing the boundary from the preamble header. |
||||
|
||||
|
||||
|
||||
_PartStream_ is a _ReadableStream_ |
||||
|
||||
PartStream (special) events |
||||
--------------------------- |
||||
|
||||
* **header**(< _object_ >header) - An object containing the header for this particular part. Each property value is an _array_ of one or more string values. |
@ -0,0 +1,63 @@ |
||||
var assert = require('assert'); |
||||
var Dicer = require('..'), |
||||
boundary = '-----------------------------168072824752491622650073', |
||||
d = new Dicer({ boundary: boundary }), |
||||
mb = 100, |
||||
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024), |
||||
callbacks = |
||||
{ partBegin: -1, |
||||
partEnd: -1, |
||||
headerField: -1, |
||||
headerValue: -1, |
||||
partData: -1, |
||||
end: -1, |
||||
}; |
||||
|
||||
|
||||
d.on('part', function(p) { |
||||
callbacks.partBegin++; |
||||
p.on('header', function(header) { |
||||
/*for (var h in header) |
||||
console.log('Part header: k: ' + inspect(h) + ', v: ' + inspect(header[h]));*/ |
||||
}); |
||||
p.on('data', function(data) { |
||||
callbacks.partData++; |
||||
//console.log('Part data: ' + inspect(data.toString()));
|
||||
}); |
||||
p.on('end', function() { |
||||
//console.log('End of part\n');
|
||||
callbacks.partEnd++; |
||||
}); |
||||
}); |
||||
d.on('end', function() { |
||||
//console.log('End of parts');
|
||||
callbacks.end++; |
||||
}); |
||||
|
||||
var start = +new Date(), |
||||
nparsed = d.write(buffer), |
||||
duration = +new Date - start, |
||||
mbPerSec = (mb / (duration / 1000)).toFixed(2); |
||||
|
||||
console.log(mbPerSec+' mb/sec'); |
||||
|
||||
//assert.equal(nparsed, buffer.length);
|
||||
|
||||
function createMultipartBuffer(boundary, size) { |
||||
var head = |
||||
'--'+boundary+'\r\n' |
||||
+ 'content-disposition: form-data; name="field1"\r\n' |
||||
+ '\r\n' |
||||
, tail = '\r\n--'+boundary+'--\r\n' |
||||
, buffer = Buffer.allocUnsafe(size); |
||||
|
||||
buffer.write(head, 'ascii', 0); |
||||
buffer.write(tail, 'ascii', buffer.length - tail.length); |
||||
return buffer; |
||||
} |
||||
|
||||
process.on('exit', function() { |
||||
/*for (var k in callbacks) { |
||||
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); |
||||
}*/ |
||||
}); |
@ -0,0 +1,70 @@ |
||||
var assert = require('assert'); |
||||
require('../node_modules/formidable/test/common'); |
||||
var multipartParser = require('../node_modules/formidable/lib/multipart_parser'), |
||||
MultipartParser = multipartParser.MultipartParser, |
||||
parser = new MultipartParser(), |
||||
boundary = '-----------------------------168072824752491622650073', |
||||
mb = 100, |
||||
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024), |
||||
callbacks = |
||||
{ partBegin: -1, |
||||
partEnd: -1, |
||||
headerField: -1, |
||||
headerValue: -1, |
||||
partData: -1, |
||||
end: -1, |
||||
}; |
||||
|
||||
|
||||
parser.initWithBoundary(boundary); |
||||
parser.onHeaderField = function() { |
||||
callbacks.headerField++; |
||||
}; |
||||
|
||||
parser.onHeaderValue = function() { |
||||
callbacks.headerValue++; |
||||
}; |
||||
|
||||
parser.onPartBegin = function() { |
||||
callbacks.partBegin++; |
||||
}; |
||||
|
||||
parser.onPartData = function() { |
||||
callbacks.partData++; |
||||
}; |
||||
|
||||
parser.onPartEnd = function() { |
||||
callbacks.partEnd++; |
||||
}; |
||||
|
||||
parser.onEnd = function() { |
||||
callbacks.end++; |
||||
}; |
||||
|
||||
var start = +new Date(), |
||||
nparsed = parser.write(buffer), |
||||
duration = +new Date - start, |
||||
mbPerSec = (mb / (duration / 1000)).toFixed(2); |
||||
|
||||
console.log(mbPerSec+' mb/sec'); |
||||
|
||||
//assert.equal(nparsed, buffer.length);
|
||||
|
||||
function createMultipartBuffer(boundary, size) { |
||||
var head = |
||||
'--'+boundary+'\r\n' |
||||
+ 'content-disposition: form-data; name="field1"\r\n' |
||||
+ '\r\n' |
||||
, tail = '\r\n--'+boundary+'--\r\n' |
||||
, buffer = Buffer.allocUnsafe(size); |
||||
|
||||
buffer.write(head, 'ascii', 0); |
||||
buffer.write(tail, 'ascii', buffer.length - tail.length); |
||||
return buffer; |
||||
} |
||||
|
||||
process.on('exit', function() { |
||||
/*for (var k in callbacks) { |
||||
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); |
||||
}*/ |
||||
}); |
@ -0,0 +1,56 @@ |
||||
var assert = require('assert'); |
||||
var multipartser = require('multipartser'), |
||||
boundary = '-----------------------------168072824752491622650073', |
||||
parser = multipartser(), |
||||
mb = 100, |
||||
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024), |
||||
callbacks = |
||||
{ partBegin: -1, |
||||
partEnd: -1, |
||||
headerField: -1, |
||||
headerValue: -1, |
||||
partData: -1, |
||||
end: -1, |
||||
}; |
||||
|
||||
parser.boundary( boundary ); |
||||
|
||||
parser.on( 'part', function ( part ) { |
||||
}); |
||||
|
||||
parser.on( 'end', function () { |
||||
//console.log( 'completed parsing' );
|
||||
}); |
||||
|
||||
parser.on( 'error', function ( error ) { |
||||
console.error( error ); |
||||
}); |
||||
|
||||
var start = +new Date(), |
||||
nparsed = parser.data(buffer), |
||||
nend = parser.end(), |
||||
duration = +new Date - start, |
||||
mbPerSec = (mb / (duration / 1000)).toFixed(2); |
||||
|
||||
console.log(mbPerSec+' mb/sec'); |
||||
|
||||
//assert.equal(nparsed, buffer.length);
|
||||
|
||||
function createMultipartBuffer(boundary, size) { |
||||
var head = |
||||
'--'+boundary+'\r\n' |
||||
+ 'content-disposition: form-data; name="field1"\r\n' |
||||
+ '\r\n' |
||||
, tail = '\r\n--'+boundary+'--\r\n' |
||||
, buffer = Buffer.allocUnsafe(size); |
||||
|
||||
buffer.write(head, 'ascii', 0); |
||||
buffer.write(tail, 'ascii', buffer.length - tail.length); |
||||
return buffer; |
||||
} |
||||
|
||||
process.on('exit', function() { |
||||
/*for (var k in callbacks) { |
||||
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); |
||||
}*/ |
||||
}); |
@ -0,0 +1,76 @@ |
||||
var assert = require('assert'), |
||||
Form = require('multiparty').Form, |
||||
boundary = '-----------------------------168072824752491622650073', |
||||
mb = 100, |
||||
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024), |
||||
callbacks = |
||||
{ partBegin: -1, |
||||
partEnd: -1, |
||||
headerField: -1, |
||||
headerValue: -1, |
||||
partData: -1, |
||||
end: -1, |
||||
}; |
||||
|
||||
var form = new Form({ boundary: boundary }); |
||||
|
||||
hijack('onParseHeaderField', function() { |
||||
callbacks.headerField++; |
||||
}); |
||||
|
||||
hijack('onParseHeaderValue', function() { |
||||
callbacks.headerValue++; |
||||
}); |
||||
|
||||
hijack('onParsePartBegin', function() { |
||||
callbacks.partBegin++; |
||||
}); |
||||
|
||||
hijack('onParsePartData', function() { |
||||
callbacks.partData++; |
||||
}); |
||||
|
||||
hijack('onParsePartEnd', function() { |
||||
callbacks.partEnd++; |
||||
}); |
||||
|
||||
form.on('finish', function() { |
||||
callbacks.end++; |
||||
}); |
||||
|
||||
var start = new Date(); |
||||
form.write(buffer, function(err) { |
||||
var duration = new Date() - start; |
||||
assert.ifError(err); |
||||
var mbPerSec = (mb / (duration / 1000)).toFixed(2); |
||||
console.log(mbPerSec+' mb/sec'); |
||||
}); |
||||
|
||||
//assert.equal(nparsed, buffer.length);
|
||||
|
||||
function createMultipartBuffer(boundary, size) { |
||||
var head = |
||||
'--'+boundary+'\r\n' |
||||
+ 'content-disposition: form-data; name="field1"\r\n' |
||||
+ '\r\n' |
||||
, tail = '\r\n--'+boundary+'--\r\n' |
||||
, buffer = Buffer.allocUnsafe(size); |
||||
|
||||
buffer.write(head, 'ascii', 0); |
||||
buffer.write(tail, 'ascii', buffer.length - tail.length); |
||||
return buffer; |
||||
} |
||||
|
||||
process.on('exit', function() { |
||||
/*for (var k in callbacks) { |
||||
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); |
||||
}*/ |
||||
}); |
||||
|
||||
function hijack(name, fn) { |
||||
var oldFn = form[name]; |
||||
form[name] = function() { |
||||
fn(); |
||||
return oldFn.apply(this, arguments); |
||||
}; |
||||
} |
@ -0,0 +1,63 @@ |
||||
// A special, edited version of the multipart parser from parted is needed here
|
||||
// because otherwise it attempts to do some things above and beyond just parsing
|
||||
// -- like saving to disk and whatnot
|
||||
|
||||
var assert = require('assert'); |
||||
var Parser = require('./parted-multipart'), |
||||
boundary = '-----------------------------168072824752491622650073', |
||||
parser = new Parser('boundary=' + boundary), |
||||
mb = 100, |
||||
buffer = createMultipartBuffer(boundary, mb * 1024 * 1024), |
||||
callbacks = |
||||
{ partBegin: -1, |
||||
partEnd: -1, |
||||
headerField: -1, |
||||
headerValue: -1, |
||||
partData: -1, |
||||
end: -1, |
||||
}; |
||||
|
||||
|
||||
parser.on('header', function() { |
||||
//callbacks.headerField++;
|
||||
}); |
||||
|
||||
parser.on('data', function() { |
||||
//callbacks.partBegin++;
|
||||
}); |
||||
|
||||
parser.on('part', function() { |
||||
|
||||
}); |
||||
|
||||
parser.on('end', function() { |
||||
//callbacks.end++;
|
||||
}); |
||||
|
||||
var start = +new Date(), |
||||
nparsed = parser.write(buffer), |
||||
duration = +new Date - start, |
||||
mbPerSec = (mb / (duration / 1000)).toFixed(2); |
||||
|
||||
console.log(mbPerSec+' mb/sec'); |
||||
|
||||
//assert.equal(nparsed, buffer.length);
|
||||
|
||||
function createMultipartBuffer(boundary, size) { |
||||
var head = |
||||
'--'+boundary+'\r\n' |
||||
+ 'content-disposition: form-data; name="field1"\r\n' |
||||
+ '\r\n' |
||||
, tail = '\r\n--'+boundary+'--\r\n' |
||||
, buffer = Buffer.allocUnsafe(size); |
||||
|
||||
buffer.write(head, 'ascii', 0); |
||||
buffer.write(tail, 'ascii', buffer.length - tail.length); |
||||
return buffer; |
||||
} |
||||
|
||||
process.on('exit', function() { |
||||
/*for (var k in callbacks) { |
||||
assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); |
||||
}*/ |
||||
}); |
@ -0,0 +1,485 @@ |
||||
/** |
||||
* Parted (https://github.com/chjj/parted)
|
||||
* A streaming multipart state parser. |
||||
* Copyright (c) 2011, Christopher Jeffrey. (MIT Licensed) |
||||
*/ |
||||
|
||||
var fs = require('fs') |
||||
, path = require('path') |
||||
, EventEmitter = require('events').EventEmitter |
||||
, StringDecoder = require('string_decoder').StringDecoder |
||||
, set = require('qs').set |
||||
, each = Array.prototype.forEach; |
||||
|
||||
/** |
||||
* Character Constants |
||||
*/ |
||||
|
||||
var DASH = '-'.charCodeAt(0) |
||||
, CR = '\r'.charCodeAt(0) |
||||
, LF = '\n'.charCodeAt(0) |
||||
, COLON = ':'.charCodeAt(0) |
||||
, SPACE = ' '.charCodeAt(0); |
||||
|
||||
/** |
||||
* Parser |
||||
*/ |
||||
|
||||
var Parser = function(type, options) { |
||||
if (!(this instanceof Parser)) { |
||||
return new Parser(type, options); |
||||
} |
||||
|
||||
EventEmitter.call(this); |
||||
|
||||
this.writable = true; |
||||
this.readable = true; |
||||
|
||||
this.options = options || {}; |
||||
|
||||
var key = grab(type, 'boundary'); |
||||
if (!key) { |
||||
return this._error('No boundary key found.'); |
||||
} |
||||
|
||||
this.key = Buffer.allocUnsafe('\r\n--' + key); |
||||
|
||||
this._key = {}; |
||||
each.call(this.key, function(ch) { |
||||
this._key[ch] = true; |
||||
}, this); |
||||
|
||||
this.state = 'start'; |
||||
this.pending = 0; |
||||
this.written = 0; |
||||
this.writtenDisk = 0; |
||||
this.buff = Buffer.allocUnsafe(200); |
||||
|
||||
this.preamble = true; |
||||
this.epilogue = false; |
||||
|
||||
this._reset(); |
||||
}; |
||||
|
||||
Parser.prototype.__proto__ = EventEmitter.prototype; |
||||
|
||||
/** |
||||
* Parsing |
||||
*/ |
||||
|
||||
Parser.prototype.write = function(data) { |
||||
if (!this.writable |
||||
|| this.epilogue) return; |
||||
|
||||
try { |
||||
this._parse(data); |
||||
} catch (e) { |
||||
this._error(e); |
||||
} |
||||
|
||||
return true; |
||||
}; |
||||
|
||||
Parser.prototype.end = function(data) { |
||||
if (!this.writable) return; |
||||
|
||||
if (data) this.write(data); |
||||
|
||||
if (!this.epilogue) { |
||||
return this._error('Message underflow.'); |
||||
} |
||||
|
||||
return true; |
||||
}; |
||||
|
||||
Parser.prototype._parse = function(data) { |
||||
var i = 0 |
||||
, len = data.length |
||||
, buff = this.buff |
||||
, key = this.key |
||||
, ch |
||||
, val |
||||
, j; |
||||
|
||||
for (; i < len; i++) { |
||||
if (this.pos >= 200) { |
||||
return this._error('Potential buffer overflow.'); |
||||
} |
||||
|
||||
ch = data[i]; |
||||
|
||||
switch (this.state) { |
||||
case 'start': |
||||
switch (ch) { |
||||
case DASH: |
||||
this.pos = 3; |
||||
this.state = 'key'; |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
break; |
||||
case 'key': |
||||
if (this.pos === key.length) { |
||||
this.state = 'key_end'; |
||||
i--; |
||||
} else if (ch !== key[this.pos]) { |
||||
if (this.preamble) { |
||||
this.state = 'start'; |
||||
i--; |
||||
} else { |
||||
this.state = 'body'; |
||||
val = this.pos - i; |
||||
if (val > 0) { |
||||
this._write(key.slice(0, val)); |
||||
} |
||||
i--; |
||||
} |
||||
} else { |
||||
this.pos++; |
||||
} |
||||
break; |
||||
case 'key_end': |
||||
switch (ch) { |
||||
case CR: |
||||
this.state = 'key_line_end'; |
||||
break; |
||||
case DASH: |
||||
this.state = 'key_dash_end'; |
||||
break; |
||||
default: |
||||
return this._error('Expected CR or DASH.'); |
||||
} |
||||
break; |
||||
case 'key_line_end': |
||||
switch (ch) { |
||||
case LF: |
||||
if (this.preamble) { |
||||
this.preamble = false; |
||||
} else { |
||||
this._finish(); |
||||
} |
||||
this.state = 'header_name'; |
||||
this.pos = 0; |
||||
break; |
||||
default: |
||||
return this._error('Expected CR.'); |
||||
} |
||||
break; |
||||
case 'key_dash_end': |
||||
switch (ch) { |
||||
case DASH: |
||||
this.epilogue = true; |
||||
this._finish(); |
||||
return; |
||||
default: |
||||
return this._error('Expected DASH.'); |
||||
} |
||||
break; |
||||
case 'header_name': |
||||
switch (ch) { |
||||
case COLON: |
||||
this.header = buff.toString('ascii', 0, this.pos); |
||||
this.pos = 0; |
||||
this.state = 'header_val'; |
||||
break; |
||||
default: |
||||
buff[this.pos++] = ch | 32; |
||||
break; |
||||
} |
||||
break; |
||||
case 'header_val': |
||||
switch (ch) { |
||||
case CR: |
||||
this.state = 'header_val_end'; |
||||
break; |
||||
case SPACE: |
||||
if (this.pos === 0) { |
||||
break; |
||||
} |
||||
; // FALL-THROUGH
|
||||
default: |
||||
buff[this.pos++] = ch; |
||||
break; |
||||
} |
||||
break; |
||||
case 'header_val_end': |
||||
switch (ch) { |
||||
case LF: |
||||
val = buff.toString('ascii', 0, this.pos); |
||||
this._header(this.header, val); |
||||
this.pos = 0; |
||||
this.state = 'header_end'; |
||||
break; |
||||
default: |
||||
return this._error('Expected LF.'); |
||||
} |
||||
break; |
||||
case 'header_end': |
||||
switch (ch) { |
||||
case CR: |
||||
this.state = 'head_end'; |
||||
break; |
||||
default: |
||||
this.state = 'header_name'; |
||||
i--; |
||||
break; |
||||
} |
||||
break; |
||||
case 'head_end': |
||||
switch (ch) { |
||||
case LF: |
||||
this.state = 'body'; |
||||
i++; |
||||
if (i >= len) return; |
||||
data = data.slice(i); |
||||
i = -1; |
||||
len = data.length; |
||||
break; |
||||
default: |
||||
return this._error('Expected LF.'); |
||||
} |
||||
break; |
||||
case 'body': |
||||
switch (ch) { |
||||
case CR: |
||||
if (i > 0) { |
||||
this._write(data.slice(0, i)); |
||||
} |
||||
this.pos = 1; |
||||
this.state = 'key'; |
||||
data = data.slice(i); |
||||
i = 0; |
||||
len = data.length; |
||||
break; |
||||
default: |
||||
// boyer-moore-like algorithm
|
||||
// at felixge's suggestion
|
||||
while ((j = i + key.length - 1) < len) { |
||||
if (this._key[data[j]]) break; |
||||
i = j; |
||||
} |
||||
break; |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
|
||||
if (this.state === 'body') { |
||||
this._write(data); |
||||
} |
||||
}; |
||||
|
||||
Parser.prototype._header = function(name, val) { |
||||
/*if (name === 'content-disposition') { |
||||
this.field = grab(val, 'name'); |
||||
this.file = grab(val, 'filename'); |
||||
|
||||
if (this.file) { |
||||
this.data = stream(this.file, this.options.path); |
||||
} else { |
||||
this.decode = new StringDecoder('utf8'); |
||||
this.data = ''; |
||||
} |
||||
}*/ |
||||
|
||||
return this.emit('header', name, val); |
||||
}; |
||||
|
||||
Parser.prototype._write = function(data) { |
||||
/*if (this.data == null) { |
||||
return this._error('No disposition.'); |
||||
} |
||||
|
||||
if (this.file) { |
||||
this.data.write(data); |
||||
this.writtenDisk += data.length; |
||||
} else { |
||||
this.data += this.decode.write(data); |
||||
this.written += data.length; |
||||
}*/ |
||||
|
||||
this.emit('data', data); |
||||
}; |
||||
|
||||
Parser.prototype._reset = function() { |
||||
this.pos = 0; |
||||
this.decode = null; |
||||
this.field = null; |
||||
this.data = null; |
||||
this.file = null; |
||||
this.header = null; |
||||
}; |
||||
|
||||
Parser.prototype._error = function(err) { |
||||
this.destroy(); |
||||
this.emit('error', typeof err === 'string' |
||||
? new Error(err) |
||||
: err); |
||||
}; |
||||
|
||||
Parser.prototype.destroy = function(err) { |
||||
this.writable = false; |
||||
this.readable = false; |
||||
this._reset(); |
||||
}; |
||||
|
||||
Parser.prototype._finish = function() { |
||||
var self = this |
||||
, field = this.field |
||||
, data = this.data |
||||
, file = this.file |
||||
, part; |
||||
|
||||
this.pending++; |
||||
|
||||
this._reset(); |
||||
|
||||
if (data && data.path) { |
||||
part = data.path; |
||||
data.end(next); |
||||
} else { |
||||
part = data; |
||||
next(); |
||||
} |
||||
|
||||
function next() { |
||||
if (!self.readable) return; |
||||
|
||||
self.pending--; |
||||
|
||||
self.emit('part', field, part); |
||||
|
||||
if (data && data.path) { |
||||
self.emit('file', field, part, file); |
||||
} |
||||
|
||||
if (self.epilogue && !self.pending) { |
||||
self.emit('end'); |
||||
self.destroy(); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
/** |
||||
* Uploads |
||||
*/ |
||||
|
||||
Parser.root = process.platform === 'win32' |
||||
? 'C:/Temp' |
||||
: '/tmp'; |
||||
|
||||
/** |
||||
* Middleware |
||||
*/ |
||||
|
||||
Parser.middleware = function(options) { |
||||
options = options || {}; |
||||
return function(req, res, next) { |
||||
if (options.ensureBody) { |
||||
req.body = {}; |
||||
} |
||||
|
||||
if (req.method === 'GET' |
||||
|| req.method === 'HEAD' |
||||
|| req._multipart) return next(); |
||||
|
||||
req._multipart = true; |
||||
|
||||
var type = req.headers['content-type']; |
||||
|
||||
if (type) type = type.split(';')[0].trim().toLowerCase(); |
||||
|
||||
if (type === 'multipart/form-data') { |
||||
Parser.handle(req, res, next, options); |
||||
} else { |
||||
next(); |
||||
} |
||||
}; |
||||
}; |
||||
|
||||
/** |
||||
* Handler |
||||
*/ |
||||
|
||||
Parser.handle = function(req, res, next, options) { |
||||
var parser = new Parser(req.headers['content-type'], options) |
||||
, diskLimit = options.diskLimit |
||||
, limit = options.limit |
||||
, parts = {} |
||||
, files = {}; |
||||
|
||||
parser.on('error', function(err) { |
||||
req.destroy(); |
||||
next(err); |
||||
}); |
||||
|
||||
parser.on('part', function(field, part) { |
||||
set(parts, field, part); |
||||
}); |
||||
|
||||
parser.on('file', function(field, path, name) { |
||||
set(files, field, { |
||||
path: path, |
||||
name: name, |
||||
toString: function() { |
||||
return path; |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
parser.on('data', function() { |
||||
if (this.writtenDisk > diskLimit || this.written > limit) { |
||||
this.emit('error', new Error('Overflow.')); |
||||
this.destroy(); |
||||
} |
||||
}); |
||||
|
||||
parser.on('end', next); |
||||
|
||||
req.body = parts; |
||||
req.files = files; |
||||
req.pipe(parser); |
||||
}; |
||||
|
||||
/** |
||||
* Helpers |
||||
*/ |
||||
|
||||
var isWindows = process.platform === 'win32'; |
||||
|
||||
var stream = function(name, dir) { |
||||
var ext = path.extname(name) || '' |
||||
, name = path.basename(name, ext) || '' |
||||
, dir = dir || Parser.root |
||||
, tag; |
||||
|
||||
tag = Math.random().toString(36).substring(2); |
||||
|
||||
name = name.substring(0, 200) + '.' + tag; |
||||
name = path.join(dir, name) + ext.substring(0, 6); |
||||
name = name.replace(/\0/g, ''); |
||||
|
||||
if (isWindows) { |
||||
name = name.replace(/[:*<>|"?]/g, ''); |
||||
} |
||||
|
||||
return fs.createWriteStream(name); |
||||
}; |
||||
|
||||
var grab = function(str, name) { |
||||
if (!str) return; |
||||
|
||||
var rx = new RegExp('\\b' + name + '\\s*=\\s*("[^"]+"|\'[^\']+\'|[^;,]+)', 'i') |
||||
, cap = rx.exec(str); |
||||
|
||||
if (cap) { |
||||
return cap[1].trim().replace(/^['"]|['"]$/g, ''); |
||||
} |
||||
}; |
||||
|
||||
/** |
||||
* Expose |
||||
*/ |
||||
|
||||
module.exports = Parser; |
@ -0,0 +1,239 @@ |
||||
var WritableStream = require('stream').Writable, |
||||
inherits = require('util').inherits; |
||||
|
||||
var StreamSearch = require('streamsearch'); |
||||
|
||||
var PartStream = require('./PartStream'), |
||||
HeaderParser = require('./HeaderParser'); |
||||
|
||||
var DASH = 45, |
||||
B_ONEDASH = Buffer.from('-'), |
||||
B_CRLF = Buffer.from('\r\n'), |
||||
EMPTY_FN = function() {}; |
||||
|
||||
function Dicer(cfg) { |
||||
if (!(this instanceof Dicer)) |
||||
return new Dicer(cfg); |
||||
WritableStream.call(this, cfg); |
||||
|
||||
if (!cfg || (!cfg.headerFirst && typeof cfg.boundary !== 'string')) |
||||
throw new TypeError('Boundary required'); |
||||
|
||||
if (typeof cfg.boundary === 'string') |
||||
this.setBoundary(cfg.boundary); |
||||
else |
||||
this._bparser = undefined; |
||||
|
||||
this._headerFirst = cfg.headerFirst; |
||||
|
||||
var self = this; |
||||
|
||||
this._dashes = 0; |
||||
this._parts = 0; |
||||
this._finished = false; |
||||
this._realFinish = false; |
||||
this._isPreamble = true; |
||||
this._justMatched = false; |
||||
this._firstWrite = true; |
||||
this._inHeader = true; |
||||
this._part = undefined; |
||||
this._cb = undefined; |
||||
this._ignoreData = false; |
||||
this._partOpts = (typeof cfg.partHwm === 'number' |
||||
? { highWaterMark: cfg.partHwm } |
||||
: {}); |
||||
this._pause = false; |
||||
|
||||
this._hparser = new HeaderParser(cfg); |
||||
this._hparser.on('header', function(header) { |
||||
self._inHeader = false; |
||||
self._part.emit('header', header); |
||||
}); |
||||
|
||||
} |
||||
inherits(Dicer, WritableStream); |
||||
|
||||
Dicer.prototype.emit = function(ev) { |
||||
if (ev === 'finish' && !this._realFinish) { |
||||
if (!this._finished) { |
||||
var self = this; |
||||
process.nextTick(function() { |
||||
self.emit('error', new Error('Unexpected end of multipart data')); |
||||
if (self._part && !self._ignoreData) { |
||||
var type = (self._isPreamble ? 'Preamble' : 'Part'); |
||||
self._part.emit('error', new Error(type + ' terminated early due to unexpected end of multipart data')); |
||||
self._part.push(null); |
||||
process.nextTick(function() { |
||||
self._realFinish = true; |
||||
self.emit('finish'); |
||||
self._realFinish = false; |
||||
}); |
||||
return; |
||||
} |
||||
self._realFinish = true; |
||||
self.emit('finish'); |
||||
self._realFinish = false; |
||||
}); |
||||
} |
||||
} else |
||||
WritableStream.prototype.emit.apply(this, arguments); |
||||
}; |
||||
|
||||
Dicer.prototype._write = function(data, encoding, cb) { |
||||
// ignore unexpected data (e.g. extra trailer data after finished)
|
||||
if (!this._hparser && !this._bparser) |
||||
return cb(); |
||||
|
||||
if (this._headerFirst && this._isPreamble) { |
||||
if (!this._part) { |
||||
this._part = new PartStream(this._partOpts); |
||||
if (this._events.preamble) |
||||
this.emit('preamble', this._part); |
||||
else |
||||
this._ignore(); |
||||
} |
||||
var r = this._hparser.push(data); |
||||
if (!this._inHeader && r !== undefined && r < data.length) |
||||
data = data.slice(r); |
||||
else |
||||
return cb(); |
||||
} |
||||
|
||||
// allows for "easier" testing
|
||||
if (this._firstWrite) { |
||||
this._bparser.push(B_CRLF); |
||||
this._firstWrite = false; |
||||
} |
||||
|
||||
this._bparser.push(data); |
||||
|
||||
if (this._pause) |
||||
this._cb = cb; |
||||
else |
||||
cb(); |
||||
}; |
||||
|
||||
Dicer.prototype.reset = function() { |
||||
this._part = undefined; |
||||
this._bparser = undefined; |
||||
this._hparser = undefined; |
||||
}; |
||||
|
||||
Dicer.prototype.setBoundary = function(boundary) { |
||||
var self = this; |
||||
this._bparser = new StreamSearch('\r\n--' + boundary); |
||||
this._bparser.on('info', function(isMatch, data, start, end) { |
||||
self._oninfo(isMatch, data, start, end); |
||||
}); |
||||
}; |
||||
|
||||
Dicer.prototype._ignore = function() { |
||||
if (this._part && !this._ignoreData) { |
||||
this._ignoreData = true; |
||||
this._part.on('error', EMPTY_FN); |
||||
// we must perform some kind of read on the stream even though we are
|
||||
// ignoring the data, otherwise node's Readable stream will not emit 'end'
|
||||
// after pushing null to the stream
|
||||
this._part.resume(); |
||||
} |
||||
}; |
||||
|
||||
Dicer.prototype._oninfo = function(isMatch, data, start, end) { |
||||
var buf, self = this, i = 0, r, ev, shouldWriteMore = true; |
||||
|
||||
if (!this._part && this._justMatched && data) { |
||||
while (this._dashes < 2 && (start + i) < end) { |
||||
if (data[start + i] === DASH) { |
||||
++i; |
||||
++this._dashes; |
||||
} else { |
||||
if (this._dashes) |
||||
buf = B_ONEDASH; |
||||
this._dashes = 0; |
||||
break; |
||||
} |
||||
} |
||||
if (this._dashes === 2) { |
||||
if ((start + i) < end && this._events.trailer) |
||||
this.emit('trailer', data.slice(start + i, end)); |
||||
this.reset(); |
||||
this._finished = true; |
||||
// no more parts will be added
|
||||
if (self._parts === 0) { |
||||
self._realFinish = true; |
||||
self.emit('finish'); |
||||
self._realFinish = false; |
||||
} |
||||
} |
||||
if (this._dashes) |
||||
return; |
||||
} |
||||
if (this._justMatched) |
||||
this._justMatched = false; |
||||
if (!this._part) { |
||||
this._part = new PartStream(this._partOpts); |
||||
this._part._read = function(n) { |
||||
self._unpause(); |
||||
}; |
||||
ev = this._isPreamble ? 'preamble' : 'part'; |
||||
if (this._events[ev]) |
||||
this.emit(ev, this._part); |
||||
else |
||||
this._ignore(); |
||||
if (!this._isPreamble) |
||||
this._inHeader = true; |
||||
} |
||||
if (data && start < end && !this._ignoreData) { |
||||
if (this._isPreamble || !this._inHeader) { |
||||
if (buf) |
||||
shouldWriteMore = this._part.push(buf); |
||||
shouldWriteMore = this._part.push(data.slice(start, end)); |
||||
if (!shouldWriteMore) |
||||
this._pause = true; |
||||
} else if (!this._isPreamble && this._inHeader) { |
||||
if (buf) |
||||
this._hparser.push(buf); |
||||
r = this._hparser.push(data.slice(start, end)); |
||||
if (!this._inHeader && r !== undefined && r < end) |
||||
this._oninfo(false, data, start + r, end); |
||||
} |
||||
} |
||||
if (isMatch) { |
||||
this._hparser.reset(); |
||||
if (this._isPreamble) |
||||
this._isPreamble = false; |
||||
else { |
||||
++this._parts; |
||||
this._part.on('end', function() { |
||||
if (--self._parts === 0) { |
||||
if (self._finished) { |
||||
self._realFinish = true; |
||||
self.emit('finish'); |
||||
self._realFinish = false; |
||||
} else { |
||||
self._unpause(); |
||||
} |
||||
} |
||||
}); |
||||
} |
||||
this._part.push(null); |
||||
this._part = undefined; |
||||
this._ignoreData = false; |
||||
this._justMatched = true; |
||||
this._dashes = 0; |
||||
} |
||||
}; |
||||
|
||||
Dicer.prototype._unpause = function() { |
||||
if (!this._pause) |
||||
return; |
||||
|
||||
this._pause = false; |
||||
if (this._cb) { |
||||
var cb = this._cb; |
||||
this._cb = undefined; |
||||
cb(); |
||||
} |
||||
}; |
||||
|
||||
module.exports = Dicer; |
@ -0,0 +1,110 @@ |
||||
var EventEmitter = require('events').EventEmitter, |
||||
inherits = require('util').inherits; |
||||
|
||||
var StreamSearch = require('streamsearch'); |
||||
|
||||
var B_DCRLF = Buffer.from('\r\n\r\n'), |
||||
RE_CRLF = /\r\n/g, |
||||
RE_HDR = /^([^:]+):[ \t]?([\x00-\xFF]+)?$/, |
||||
MAX_HEADER_PAIRS = 2000, // from node's http.js
|
||||
MAX_HEADER_SIZE = 80 * 1024; // from node's http_parser
|
||||
|
||||
function HeaderParser(cfg) { |
||||
EventEmitter.call(this); |
||||
|
||||
var self = this; |
||||
this.nread = 0; |
||||
this.maxed = false; |
||||
this.npairs = 0; |
||||
this.maxHeaderPairs = (cfg && typeof cfg.maxHeaderPairs === 'number' |
||||
? cfg.maxHeaderPairs |
||||
: MAX_HEADER_PAIRS); |
||||
this.buffer = ''; |
||||
this.header = {}; |
||||
this.finished = false; |
||||
this.ss = new StreamSearch(B_DCRLF); |
||||
this.ss.on('info', function(isMatch, data, start, end) { |
||||
if (data && !self.maxed) { |
||||
if (self.nread + (end - start) > MAX_HEADER_SIZE) { |
||||
end = (MAX_HEADER_SIZE - self.nread); |
||||
self.nread = MAX_HEADER_SIZE; |
||||
} else |
||||
self.nread += (end - start); |
||||
|
||||
if (self.nread === MAX_HEADER_SIZE) |
||||
self.maxed = true; |
||||
|
||||
self.buffer += data.toString('binary', start, end); |
||||
} |
||||
if (isMatch) |
||||
self._finish(); |
||||
}); |
||||
} |
||||
inherits(HeaderParser, EventEmitter); |
||||
|
||||
HeaderParser.prototype.push = function(data) { |
||||
var r = this.ss.push(data); |
||||
if (this.finished) |
||||
return r; |
||||
}; |
||||
|
||||
HeaderParser.prototype.reset = function() { |
||||
this.finished = false; |
||||
this.buffer = ''; |
||||
this.header = {}; |
||||
this.ss.reset(); |
||||
}; |
||||
|
||||
HeaderParser.prototype._finish = function() { |
||||
if (this.buffer) |
||||
this._parseHeader(); |
||||
this.ss.matches = this.ss.maxMatches; |
||||
var header = this.header; |
||||
this.header = {}; |
||||
this.buffer = ''; |
||||
this.finished = true; |
||||
this.nread = this.npairs = 0; |
||||
this.maxed = false; |
||||
this.emit('header', header); |
||||
}; |
||||
|
||||
HeaderParser.prototype._parseHeader = function() { |
||||
if (this.npairs === this.maxHeaderPairs) |
||||
return; |
||||
|
||||
var lines = this.buffer.split(RE_CRLF), len = lines.length, m, h, |
||||
modded = false; |
||||
|
||||
for (var i = 0; i < len; ++i) { |
||||
if (lines[i].length === 0) |
||||
continue; |
||||
if (lines[i][0] === '\t' || lines[i][0] === ' ') { |
||||
// folded header content
|
||||
// RFC2822 says to just remove the CRLF and not the whitespace following
|
||||
// it, so we follow the RFC and include the leading whitespace ...
|
||||
this.header[h][this.header[h].length - 1] += lines[i]; |
||||
} else { |
||||
m = RE_HDR.exec(lines[i]); |
||||
if (m) { |
||||
h = m[1].toLowerCase(); |
||||
if (m[2]) { |
||||
if (this.header[h] === undefined) |
||||
this.header[h] = [m[2]]; |
||||
else |
||||
this.header[h].push(m[2]); |
||||
} else |
||||
this.header[h] = ['']; |
||||
if (++this.npairs === this.maxHeaderPairs) |
||||
break; |
||||
} else { |
||||
this.buffer = lines[i]; |
||||
modded = true; |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
if (!modded) |
||||
this.buffer = ''; |
||||
}; |
||||
|
||||
module.exports = HeaderParser; |
@ -0,0 +1,11 @@ |
||||
var inherits = require('util').inherits, |
||||
ReadableStream = require('stream').Readable; |
||||
|
||||
function PartStream(opts) { |
||||
ReadableStream.call(this, opts); |
||||
} |
||||
inherits(PartStream, ReadableStream); |
||||
|
||||
PartStream.prototype._read = function(n) {}; |
||||
|
||||
module.exports = PartStream; |
@ -0,0 +1,66 @@ |
||||
{ |
||||
"_from": "dicer@0.3.0", |
||||
"_id": "dicer@0.3.0", |
||||
"_inBundle": false, |
||||
"_integrity": "sha512-MdceRRWqltEG2dZqO769g27N/3PXfcKl04VhYnBlo2YhH7zPi88VebsjTKclaOyiuMaGU72hTfw3VkUitGcVCA==", |
||||
"_location": "/dicer", |
||||
"_phantomChildren": {}, |
||||
"_requested": { |
||||
"type": "version", |
||||
"registry": true, |
||||
"raw": "dicer@0.3.0", |
||||
"name": "dicer", |
||||
"escapedName": "dicer", |
||||
"rawSpec": "0.3.0", |
||||
"saveSpec": null, |
||||
"fetchSpec": "0.3.0" |
||||
}, |
||||
"_requiredBy": [ |
||||
"/busboy" |
||||
], |
||||
"_resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz", |
||||
"_shasum": "eacd98b3bfbf92e8ab5c2fdb71aaac44bb06b872", |
||||
"_spec": "dicer@0.3.0", |
||||
"_where": "/home/sigonasr2/divar/server/node_modules/busboy", |
||||
"author": { |
||||
"name": "Brian White", |
||||
"email": "mscdex@mscdex.net" |
||||
}, |
||||
"bugs": { |
||||
"url": "https://github.com/mscdex/dicer/issues" |
||||
}, |
||||
"bundleDependencies": false, |
||||
"dependencies": { |
||||
"streamsearch": "0.1.2" |
||||
}, |
||||
"deprecated": false, |
||||
"description": "A very fast streaming multipart parser for node.js", |
||||
"engines": { |
||||
"node": ">=4.5.0" |
||||
}, |
||||
"homepage": "https://github.com/mscdex/dicer#readme", |
||||
"keywords": [ |
||||
"parser", |
||||
"parse", |
||||
"parsing", |
||||
"multipart", |
||||
"form-data", |
||||
"streaming" |
||||
], |
||||
"licenses": [ |
||||
{ |
||||
"type": "MIT", |
||||
"url": "http://github.com/mscdex/dicer/raw/master/LICENSE" |
||||
} |
||||
], |
||||
"main": "./lib/Dicer", |
||||
"name": "dicer", |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "git+ssh://git@github.com/mscdex/dicer.git" |
||||
}, |
||||
"scripts": { |
||||
"test": "node test/test.js" |
||||
}, |
||||
"version": "0.3.0" |
||||
} |
@ -0,0 +1,31 @@ |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="_method" |
||||
|
||||
put |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[blog]" |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[public_email]" |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[interests]" |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[bio]" |
||||
|
||||
hello |
||||
|
||||
"quote" |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="commit" |
||||
|
||||
Save |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="media"; filename="" |
||||
Content-Type: application/octet-stream |
||||
|
||||
|
@ -0,0 +1 @@ |
||||
put |
@ -0,0 +1 @@ |
||||
{"content-disposition": ["form-data; name=\"_method\""]} |
@ -0,0 +1 @@ |
||||
{"content-disposition": ["form-data; name=\"profile[blog]\""]} |
@ -0,0 +1 @@ |
||||
{"content-disposition": ["form-data; name=\"profile[public_email]\""]} |
@ -0,0 +1 @@ |
||||
{"content-disposition": ["form-data; name=\"profile[interests]\""]} |
@ -0,0 +1,3 @@ |
||||
hello |
||||
|
||||
"quote" |
@ -0,0 +1 @@ |
||||
{"content-disposition": ["form-data; name=\"profile[bio]\""]} |
@ -0,0 +1 @@ |
||||
Save |
@ -0,0 +1 @@ |
||||
{"content-disposition": ["form-data; name=\"commit\""]} |
@ -0,0 +1,2 @@ |
||||
{"content-disposition": ["form-data; name=\"media\"; filename=\"\""], |
||||
"content-type": ["application/octet-stream"]} |
@ -0,0 +1,32 @@ |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="_method" |
||||
|
||||
put |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[blog]" |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[public_email]" |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[interests]" |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[bio]" |
||||
|
||||
hello |
||||
|
||||
"quote" |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="media"; filename="" |
||||
Content-Type: application/octet-stream |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="commit" |
||||
|
||||
Save |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR-- |
@ -0,0 +1,33 @@ |
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="_method" |
||||
|
||||
put |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[blog]" |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[public_email]" |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[interests]" |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[bio]" |
||||
|
||||
hello |
||||
|
||||
"quote" |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="media"; filename="" |
||||
Content-Type: application/octet-stream |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="commit" |
||||
|
||||
Save |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR-- |
@ -0,0 +1 @@ |
||||
Preamble terminated early due to unexpected end of multipart data |
@ -0,0 +1,32 @@ |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="_method" |
||||
|
||||
put |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[blog]" |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[public_email]" |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[interests]" |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="profile[bio]" |
||||
|
||||
hello |
||||
|
||||
"quote" |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="media"; filename="" |
||||
Content-Type: application/octet-stream |
||||
|
||||
|
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR |
||||
Content-Disposition: form-data; name="commit" |
||||
|
||||
Save |
||||
------WebKitFormBoundaryWLHCs9qmcJJoyjKR-- |
@ -0,0 +1 @@ |
||||
put |
@ -0,0 +1 @@ |
||||
{"content-disposition": ["form-data; name=\"_method\""]} |
@ -0,0 +1 @@ |
||||
{"content-disposition": ["form-data; name=\"profile[blog]\""]} |
@ -0,0 +1 @@ |
||||
{"content-disposition": ["form-data; name=\"profile[public_email]\""]} |
@ -0,0 +1 @@ |
||||
{"content-disposition": ["form-data; name=\"profile[interests]\""]} |
@ -0,0 +1,3 @@ |
||||
hello |
||||
|
||||
"quote" |
@ -0,0 +1 @@ |
||||
{"content-disposition": ["form-data; name=\"profile[bio]\""]} |
@ -0,0 +1,2 @@ |
||||
{"content-disposition": ["form-data; name=\"media\"; filename=\"\""], |
||||
"content-type": ["application/octet-stream"]} |
@ -0,0 +1 @@ |
||||
Save |
@ -0,0 +1 @@ |
||||
{"content-disposition": ["form-data; name=\"commit\""]} |
@ -0,0 +1,24 @@ |
||||
User-Agent: foo bar baz |
||||
Content-Type: multipart/form-data; boundary=AaB03x |
||||
|
||||
--AaB03x |
||||
Content-Disposition: form-data; name="foo" |
||||
|
||||
bar |
||||
--AaB03x |
||||
Content-Disposition: form-data; name="files" |
||||
Content-Type: multipart/mixed, boundary=BbC04y |
||||
|
||||
--BbC04y |
||||
Content-Disposition: attachment; filename="file.txt" |
||||
Content-Type: text/plain |
||||
|
||||
contents |
||||
--BbC04y |
||||
Content-Disposition: attachment; filename="flowers.jpg" |
||||
Content-Type: image/jpeg |
||||
Content-Transfer-Encoding: binary |
||||
|
||||
contents |
||||
--BbC04y-- |
||||
--AaB03x-- |
@ -0,0 +1 @@ |
||||
bar |
@ -0,0 +1 @@ |
||||
{"content-disposition": ["form-data; name=\"foo\""]} |
@ -0,0 +1,12 @@ |
||||
--BbC04y |
||||
Content-Disposition: attachment; filename="file.txt" |
||||
Content-Type: text/plain |
||||
|
||||
contents |
||||
--BbC04y |
||||
Content-Disposition: attachment; filename="flowers.jpg" |
||||
Content-Type: image/jpeg |
||||
Content-Transfer-Encoding: binary |
||||
|
||||
contents |
||||
--BbC04y-- |
@ -0,0 +1,2 @@ |
||||
{"content-disposition": ["form-data; name=\"files\""], |
||||
"content-type": ["multipart/mixed, boundary=BbC04y"]} |
@ -0,0 +1,2 @@ |
||||
{"user-agent": ["foo bar baz"], |
||||
"content-type": ["multipart/form-data; boundary=AaB03x"]} |
@ -0,0 +1,21 @@ |
||||
--AaB03x |
||||
Content-Disposition: form-data; name="foo" |
||||
|
||||
bar |
||||
--AaB03x |
||||
Content-Disposition: form-data; name="files" |
||||
Content-Type: multipart/mixed, boundary=BbC04y |
||||
|
||||
--BbC04y |
||||
Content-Disposition: attachment; filename="file.txt" |
||||
Content-Type: text/plain |
||||
|
||||
contents |
||||
--BbC04y |
||||
Content-Disposition: attachment; filename="flowers.jpg" |
||||
Content-Type: image/jpeg |
||||
Content-Transfer-Encoding: binary |
||||
|
||||
contents |
||||
--BbC04y-- |
||||
--AaB03x-- |
@ -0,0 +1 @@ |
||||
bar |
@ -0,0 +1 @@ |
||||
{"content-disposition": ["form-data; name=\"foo\""]} |
@ -0,0 +1,12 @@ |
||||
--BbC04y |
||||
Content-Disposition: attachment; filename="file.txt" |
||||
Content-Type: text/plain |
||||
|
||||
contents |
||||
--BbC04y |
||||
Content-Disposition: attachment; filename="flowers.jpg" |
||||
Content-Type: image/jpeg |
||||
Content-Transfer-Encoding: binary |
||||
|
||||
contents |
||||
--BbC04y-- |
@ -0,0 +1,2 @@ |
||||
{"content-disposition": ["form-data; name=\"files\""], |
||||
"content-type": ["multipart/mixed, boundary=BbC04y"]} |
@ -0,0 +1,87 @@ |
||||
var Dicer = require('..'); |
||||
var assert = require('assert'); |
||||
|
||||
var CRLF = '\r\n'; |
||||
var boundary = 'boundary'; |
||||
|
||||
var writeSep = '--' + boundary; |
||||
|
||||
var writePart = [ |
||||
writeSep, |
||||
'Content-Type: text/plain', |
||||
'Content-Length: 0' |
||||
].join(CRLF) |
||||
+ CRLF + CRLF |
||||
+ 'some data' + CRLF; |
||||
|
||||
var writeEnd = '--' + CRLF; |
||||
|
||||
var firedEnd = false; |
||||
var firedFinish = false; |
||||
|
||||
var dicer = new Dicer({boundary: boundary}); |
||||
dicer.on('part', partListener); |
||||
dicer.on('finish', finishListener); |
||||
dicer.write(writePart+writeSep); |
||||
|
||||
function partListener(partReadStream) { |
||||
partReadStream.on('data', function(){}); |
||||
partReadStream.on('end', partEndListener); |
||||
} |
||||
function partEndListener() { |
||||
firedEnd = true; |
||||
setImmediate(afterEnd); |
||||
} |
||||
function afterEnd() { |
||||
dicer.end(writeEnd); |
||||
setImmediate(afterWrite); |
||||
} |
||||
function finishListener() { |
||||
assert(firedEnd, 'Failed to end before finishing'); |
||||
firedFinish = true; |
||||
test2(); |
||||
} |
||||
function afterWrite() { |
||||
assert(firedFinish, 'Failed to finish'); |
||||
} |
||||
|
||||
var isPausePush = true; |
||||
|
||||
var firedPauseCallback = false; |
||||
var firedPauseFinish = false; |
||||
|
||||
var dicer2 = null; |
||||
|
||||
function test2() { |
||||
dicer2 = new Dicer({boundary: boundary}); |
||||
dicer2.on('part', pausePartListener); |
||||
dicer2.on('finish', pauseFinish); |
||||
dicer2.write(writePart+writeSep, 'utf8', pausePartCallback); |
||||
setImmediate(pauseAfterWrite); |
||||
} |
||||
function pausePartListener(partReadStream) { |
||||
partReadStream.on('data', function(){}); |
||||
partReadStream.on('end', function(){}); |
||||
var realPush = partReadStream.push; |
||||
partReadStream.push = function fakePush() { |
||||
realPush.apply(partReadStream, arguments); |
||||
if (!isPausePush) |
||||
return true; |
||||
isPausePush = false; |
||||
return false; |
||||
}; |
||||
} |
||||
function pauseAfterWrite() { |
||||
dicer2.end(writeEnd); |
||||
setImmediate(pauseAfterEnd); |
||||
} |
||||
function pauseAfterEnd() { |
||||
assert(firedPauseCallback, 'Failed to call callback after pause'); |
||||
assert(firedPauseFinish, 'Failed to finish after pause'); |
||||
} |
||||
function pauseFinish() { |
||||
firedPauseFinish = true; |
||||
} |
||||
function pausePartCallback() { |
||||
firedPauseCallback = true; |
||||
} |
@ -0,0 +1,68 @@ |
||||
var assert = require('assert'), |
||||
path = require('path'); |
||||
|
||||
var HeaderParser = require('../lib/HeaderParser'); |
||||
|
||||
var DCRLF = '\r\n\r\n', |
||||
MAXED_BUFFER = Buffer.allocUnsafe(128 * 1024); |
||||
MAXED_BUFFER.fill(0x41); // 'A'
|
||||
|
||||
var group = path.basename(__filename, '.js') + '/'; |
||||
|
||||
[ |
||||
{ source: DCRLF, |
||||
expected: {}, |
||||
what: 'No header' |
||||
}, |
||||
{ source: ['Content-Type:\t text/plain', |
||||
'Content-Length:0' |
||||
].join('\r\n') + DCRLF, |
||||
expected: {'content-type': [' text/plain'], 'content-length': ['0']}, |
||||
what: 'Value spacing' |
||||
}, |
||||
{ source: ['Content-Type:\r\n text/plain', |
||||
'Foo:\r\n bar\r\n baz', |
||||
].join('\r\n') + DCRLF, |
||||
expected: {'content-type': [' text/plain'], 'foo': [' bar baz']}, |
||||
what: 'Folded values' |
||||
}, |
||||
{ source: ['Content-Type:', |
||||
'Foo: ', |
||||
].join('\r\n') + DCRLF, |
||||
expected: {'content-type': [''], 'foo': ['']}, |
||||
what: 'Empty values' |
||||
}, |
||||
{ source: MAXED_BUFFER.toString('ascii') + DCRLF, |
||||
expected: {}, |
||||
what: 'Max header size (single chunk)' |
||||
}, |
||||
{ source: ['ABCDEFGHIJ', MAXED_BUFFER.toString('ascii'), DCRLF], |
||||
expected: {}, |
||||
what: 'Max header size (multiple chunks #1)' |
||||
}, |
||||
{ source: [MAXED_BUFFER.toString('ascii'), MAXED_BUFFER.toString('ascii'), DCRLF], |
||||
expected: {}, |
||||
what: 'Max header size (multiple chunk #2)' |
||||
}, |
||||
].forEach(function(v) { |
||||
var parser = new HeaderParser(), |
||||
fired = false; |
||||
|
||||
parser.on('header', function(header) { |
||||
assert(!fired, makeMsg(v.what, 'Header event fired more than once')); |
||||
fired = true; |
||||
assert.deepEqual(header, |
||||
v.expected, |
||||
makeMsg(v.what, 'Parsed result mismatch')); |
||||
}); |
||||
if (!Array.isArray(v.source)) |
||||
v.source = [v.source]; |
||||
v.source.forEach(function(s) { |
||||
parser.push(s); |
||||
}); |
||||
assert(fired, makeMsg(v.what, 'Did not receive header from parser')); |
||||
}); |
||||
|
||||
function makeMsg(what, msg) { |
||||
return '[' + group + what + ']: ' + msg; |
||||
} |
@ -0,0 +1,148 @@ |
||||
var Dicer = require('..'); |
||||
var assert = require('assert'), |
||||
fs = require('fs'), |
||||
path = require('path'), |
||||
inspect = require('util').inspect; |
||||
|
||||
var FIXTURES_ROOT = __dirname + '/fixtures/'; |
||||
|
||||
var t = 0, |
||||
group = path.basename(__filename, '.js') + '/'; |
||||
|
||||
var tests = [ |
||||
{ source: 'many', |
||||
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }, |
||||
chsize: 16, |
||||
nparts: 7, |
||||
what: 'Extra trailer data pushed after finished' |
||||
}, |
||||
]; |
||||
|
||||
function next() { |
||||
if (t === tests.length) |
||||
return; |
||||
var v = tests[t], |
||||
fixtureBase = FIXTURES_ROOT + v.source, |
||||
fd, |
||||
n = 0, |
||||
buffer = Buffer.allocUnsafe(v.chsize), |
||||
state = { parts: [] }; |
||||
|
||||
fd = fs.openSync(fixtureBase + '/original', 'r'); |
||||
|
||||
var dicer = new Dicer(v.opts), |
||||
error, |
||||
partErrors = 0, |
||||
finishes = 0; |
||||
|
||||
dicer.on('part', function(p) { |
||||
var part = { |
||||
body: undefined, |
||||
bodylen: 0, |
||||
error: undefined, |
||||
header: undefined |
||||
}; |
||||
|
||||
p.on('header', function(h) { |
||||
part.header = h; |
||||
}).on('data', function(data) { |
||||
// make a copy because we are using readSync which re-uses a buffer ...
|
||||
var copy = Buffer.allocUnsafe(data.length); |
||||
data.copy(copy); |
||||
data = copy; |
||||
if (!part.body) |
||||
part.body = [ data ]; |
||||
else |
||||
part.body.push(data); |
||||
part.bodylen += data.length; |
||||
}).on('error', function(err) { |
||||
part.error = err; |
||||
++partErrors; |
||||
}).on('end', function() { |
||||
if (part.body) |
||||
part.body = Buffer.concat(part.body, part.bodylen); |
||||
state.parts.push(part); |
||||
}); |
||||
}).on('error', function(err) { |
||||
error = err; |
||||
}).on('finish', function() { |
||||
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times')); |
||||
|
||||
if (v.dicerError) |
||||
assert(error !== undefined, makeMsg(v.what, 'Expected error')); |
||||
else |
||||
assert(error === undefined, makeMsg(v.what, 'Unexpected error')); |
||||
|
||||
if (v.events && v.events.indexOf('part') > -1) { |
||||
assert.equal(state.parts.length, |
||||
v.nparts, |
||||
makeMsg(v.what, |
||||
'Part count mismatch:\nActual: ' |
||||
+ state.parts.length |
||||
+ '\nExpected: ' |
||||
+ v.nparts)); |
||||
|
||||
if (!v.npartErrors) |
||||
v.npartErrors = 0; |
||||
assert.equal(partErrors, |
||||
v.npartErrors, |
||||
makeMsg(v.what, |
||||
'Part errors mismatch:\nActual: ' |
||||
+ partErrors |
||||
+ '\nExpected: ' |
||||
+ v.npartErrors)); |
||||
|
||||
for (var i = 0, header, body; i < v.nparts; ++i) { |
||||
if (fs.existsSync(fixtureBase + '/part' + (i+1))) { |
||||
body = fs.readFileSync(fixtureBase + '/part' + (i+1)); |
||||
if (body.length === 0) |
||||
body = undefined; |
||||
} else |
||||
body = undefined; |
||||
assert.deepEqual(state.parts[i].body, |
||||
body, |
||||
makeMsg(v.what, |
||||
'Part #' + (i+1) + ' body mismatch')); |
||||
if (fs.existsSync(fixtureBase + '/part' + (i+1) + '.header')) { |
||||
header = fs.readFileSync(fixtureBase |
||||
+ '/part' + (i+1) + '.header', 'binary'); |
||||
header = JSON.parse(header); |
||||
} else |
||||
header = undefined; |
||||
assert.deepEqual(state.parts[i].header, |
||||
header, |
||||
makeMsg(v.what, |
||||
'Part #' + (i+1) |
||||
+ ' parsed header mismatch:\nActual: ' |
||||
+ inspect(state.parts[i].header) |
||||
+ '\nExpected: ' |
||||
+ inspect(header))); |
||||
} |
||||
} |
||||
++t; |
||||
next(); |
||||
}); |
||||
|
||||
while (true) { |
||||
n = fs.readSync(fd, buffer, 0, buffer.length, null); |
||||
if (n === 0) { |
||||
setTimeout(function() { |
||||
dicer.write('\r\n\r\n\r\n'); |
||||
dicer.end(); |
||||
}, 50); |
||||
break; |
||||
} |
||||
dicer.write(n === buffer.length ? buffer : buffer.slice(0, n)); |
||||
} |
||||
fs.closeSync(fd); |
||||
} |
||||
next(); |
||||
|
||||
function makeMsg(what, msg) { |
||||
return '[' + group + what + ']: ' + msg; |
||||
} |
||||
|
||||
process.on('exit', function() { |
||||
assert(t === tests.length, |
||||
makeMsg('_exit', 'Only ran ' + t + '/' + tests.length + ' tests')); |
||||
}); |
@ -0,0 +1,228 @@ |
||||
var Dicer = require('..'); |
||||
var assert = require('assert'), |
||||
fs = require('fs'), |
||||
path = require('path'), |
||||
inspect = require('util').inspect; |
||||
|
||||
var FIXTURES_ROOT = __dirname + '/fixtures/'; |
||||
|
||||
var t = 0, |
||||
group = path.basename(__filename, '.js') + '/'; |
||||
|
||||
var tests = [ |
||||
{ source: 'many', |
||||
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }, |
||||
chsize: 16, |
||||
nparts: 0, |
||||
what: 'No preamble or part listeners' |
||||
}, |
||||
]; |
||||
|
||||
function next() { |
||||
if (t === tests.length) |
||||
return; |
||||
var v = tests[t], |
||||
fixtureBase = FIXTURES_ROOT + v.source, |
||||
fd, |
||||
n = 0, |
||||
buffer = Buffer.allocUnsafe(v.chsize), |
||||
state = { done: false, parts: [], preamble: undefined }; |
||||
|
||||
fd = fs.openSync(fixtureBase + '/original', 'r'); |
||||
|
||||
var dicer = new Dicer(v.opts), |
||||
error, |
||||
partErrors = 0, |
||||
finishes = 0; |
||||
|
||||
if (v.events && v.events.indexOf('preamble') > -1) { |
||||
dicer.on('preamble', function(p) { |
||||
var preamble = { |
||||
body: undefined, |
||||
bodylen: 0, |
||||
error: undefined, |
||||
header: undefined |
||||
}; |
||||
|
||||
p.on('header', function(h) { |
||||
preamble.header = h; |
||||
}).on('data', function(data) { |
||||
// make a copy because we are using readSync which re-uses a buffer ...
|
||||
var copy = Buffer.allocUnsafe(data.length); |
||||
data.copy(copy); |
||||
data = copy; |
||||
if (!preamble.body) |
||||
preamble.body = [ data ]; |
||||
else |
||||
preamble.body.push(data); |
||||
preamble.bodylen += data.length; |
||||
}).on('error', function(err) { |
||||
preamble.error = err; |
||||
}).on('end', function() { |
||||
if (preamble.body) |
||||
preamble.body = Buffer.concat(preamble.body, preamble.bodylen); |
||||
if (preamble.body || preamble.header) |
||||
state.preamble = preamble; |
||||
}); |
||||
}); |
||||
} |
||||
if (v.events && v.events.indexOf('part') > -1) { |
||||
dicer.on('part', function(p) { |
||||
var part = { |
||||
body: undefined, |
||||
bodylen: 0, |
||||
error: undefined, |
||||
header: undefined |
||||
}; |
||||
|
||||
p.on('header', function(h) { |
||||
part.header = h; |
||||
}).on('data', function(data) { |
||||
// make a copy because we are using readSync which re-uses a buffer ...
|
||||
var copy = Buffer.allocUnsafe(data.length); |
||||
data.copy(copy); |
||||
data = copy; |
||||
if (!part.body) |
||||
part.body = [ data ]; |
||||
else |
||||
part.body.push(data); |
||||
part.bodylen += data.length; |
||||
}).on('error', function(err) { |
||||
part.error = err; |
||||
++partErrors; |
||||
}).on('end', function() { |
||||
if (part.body) |
||||
part.body = Buffer.concat(part.body, part.bodylen); |
||||
state.parts.push(part); |
||||
}); |
||||
}); |
||||
} |
||||
dicer.on('error', function(err) { |
||||
error = err; |
||||
}).on('finish', function() { |
||||
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times')); |
||||
|
||||
if (v.dicerError) |
||||
assert(error !== undefined, makeMsg(v.what, 'Expected error')); |
||||
else |
||||
assert(error === undefined, makeMsg(v.what, 'Unexpected error')); |
||||
|
||||
if (v.events && v.events.indexOf('preamble') > -1) { |
||||
var preamble; |
||||
if (fs.existsSync(fixtureBase + '/preamble')) { |
||||
var prebody = fs.readFileSync(fixtureBase + '/preamble'); |
||||
if (prebody.length) { |
||||
preamble = { |
||||
body: prebody, |
||||
bodylen: prebody.length, |
||||
error: undefined, |
||||
header: undefined |
||||
}; |
||||
} |
||||
} |
||||
if (fs.existsSync(fixtureBase + '/preamble.header')) { |
||||
var prehead = JSON.parse(fs.readFileSync(fixtureBase |
||||
+ '/preamble.header', 'binary')); |
||||
if (!preamble) { |
||||
preamble = { |
||||
body: undefined, |
||||
bodylen: 0, |
||||
error: undefined, |
||||
header: prehead |
||||
}; |
||||
} else |
||||
preamble.header = prehead; |
||||
} |
||||
if (fs.existsSync(fixtureBase + '/preamble.error')) { |
||||
var err = new Error(fs.readFileSync(fixtureBase |
||||
+ '/preamble.error', 'binary')); |
||||
if (!preamble) { |
||||
preamble = { |
||||
body: undefined, |
||||
bodylen: 0, |
||||
error: err, |
||||
header: undefined |
||||
}; |
||||
} else |
||||
preamble.error = err; |
||||
} |
||||
|
||||
assert.deepEqual(state.preamble, |
||||
preamble, |
||||
makeMsg(v.what, |
||||
'Preamble mismatch:\nActual:' |
||||
+ inspect(state.preamble) |
||||
+ '\nExpected: ' |
||||
+ inspect(preamble))); |
||||
} |
||||
|
||||
if (v.events && v.events.indexOf('part') > -1) { |
||||
assert.equal(state.parts.length, |
||||
v.nparts, |
||||
makeMsg(v.what, |
||||
'Part count mismatch:\nActual: ' |
||||
+ state.parts.length |
||||
+ '\nExpected: ' |
||||
+ v.nparts)); |
||||
|
||||
if (!v.npartErrors) |
||||
v.npartErrors = 0; |
||||
assert.equal(partErrors, |
||||
v.npartErrors, |
||||
makeMsg(v.what, |
||||
'Part errors mismatch:\nActual: ' |
||||
+ partErrors |
||||
+ '\nExpected: ' |
||||
+ v.npartErrors)); |
||||
|
||||
for (var i = 0, header, body; i < v.nparts; ++i) { |
||||
if (fs.existsSync(fixtureBase + '/part' + (i+1))) { |
||||
body = fs.readFileSync(fixtureBase + '/part' + (i+1)); |
||||
if (body.length === 0) |
||||
body = undefined; |
||||
} else |
||||
body = undefined; |
||||
assert.deepEqual(state.parts[i].body, |
||||
body, |
||||
makeMsg(v.what, |
||||
'Part #' + (i+1) + ' body mismatch')); |
||||
if (fs.existsSync(fixtureBase + '/part' + (i+1) + '.header')) { |
||||
header = fs.readFileSync(fixtureBase |
||||
+ '/part' + (i+1) + '.header', 'binary'); |
||||
header = JSON.parse(header); |
||||
} else |
||||
header = undefined; |
||||
assert.deepEqual(state.parts[i].header, |
||||
header, |
||||
makeMsg(v.what, |
||||
'Part #' + (i+1) |
||||
+ ' parsed header mismatch:\nActual: ' |
||||
+ inspect(state.parts[i].header) |
||||
+ '\nExpected: ' |
||||
+ inspect(header))); |
||||
} |
||||
} |
||||
++t; |
||||
next(); |
||||
}); |
||||
|
||||
while (true) { |
||||
n = fs.readSync(fd, buffer, 0, buffer.length, null); |
||||
if (n === 0) { |
||||
dicer.end(); |
||||
break; |
||||
} |
||||
dicer.write(n === buffer.length ? buffer : buffer.slice(0, n)); |
||||
} |
||||
fs.closeSync(fd); |
||||
} |
||||
next(); |
||||
|
||||
function makeMsg(what, msg) { |
||||
return '[' + group + what + ']: ' + msg; |
||||
} |
||||
|
||||
process.on('exit', function() { |
||||
assert(t === tests.length, |
||||
makeMsg('_exit', 'Only ran ' + t + '/' + tests.length + ' tests')); |
||||
}); |
@ -0,0 +1,240 @@ |
||||
var Dicer = require('..'); |
||||
var assert = require('assert'), |
||||
fs = require('fs'), |
||||
path = require('path'), |
||||
inspect = require('util').inspect; |
||||
|
||||
var FIXTURES_ROOT = __dirname + '/fixtures/'; |
||||
|
||||
var t = 0, |
||||
group = path.basename(__filename, '.js') + '/'; |
||||
|
||||
var tests = [ |
||||
{ source: 'nested', |
||||
opts: { boundary: 'AaB03x' }, |
||||
chsize: 32, |
||||
nparts: 2, |
||||
what: 'One nested multipart' |
||||
}, |
||||
{ source: 'many', |
||||
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }, |
||||
chsize: 16, |
||||
nparts: 7, |
||||
what: 'Many parts' |
||||
}, |
||||
{ source: 'many-wrongboundary', |
||||
opts: { boundary: 'LOLOLOL' }, |
||||
chsize: 8, |
||||
nparts: 0, |
||||
dicerError: true, |
||||
what: 'Many parts, wrong boundary' |
||||
}, |
||||
{ source: 'many-noend', |
||||
opts: { boundary: '----WebKitFormBoundaryWLHCs9qmcJJoyjKR' }, |
||||
chsize: 16, |
||||
nparts: 7, |
||||
npartErrors: 1, |
||||
dicerError: true, |
||||
what: 'Many parts, end boundary missing, 1 file open' |
||||
}, |
||||
{ source: 'nested-full', |
||||
opts: { boundary: 'AaB03x', headerFirst: true }, |
||||
chsize: 32, |
||||
nparts: 2, |
||||
what: 'One nested multipart with preceding header' |
||||
}, |
||||
{ source: 'nested-full', |
||||
opts: { headerFirst: true }, |
||||
chsize: 32, |
||||
nparts: 2, |
||||
setBoundary: 'AaB03x', |
||||
what: 'One nested multipart with preceding header, using setBoundary' |
||||
}, |
||||
]; |
||||
|
||||
function next() { |
||||
if (t === tests.length) |
||||
return; |
||||
var v = tests[t], |
||||
fixtureBase = FIXTURES_ROOT + v.source, |
||||
n = 0, |
||||
buffer = Buffer.allocUnsafe(v.chsize), |
||||
state = { parts: [], preamble: undefined }; |
||||
|
||||
var dicer = new Dicer(v.opts), |
||||
error, |
||||
partErrors = 0, |
||||
finishes = 0; |
||||
|
||||
dicer.on('preamble', function(p) { |
||||
var preamble = { |
||||
body: undefined, |
||||
bodylen: 0, |
||||
error: undefined, |
||||
header: undefined |
||||
}; |
||||
|
||||
p.on('header', function(h) { |
||||
preamble.header = h; |
||||
if (v.setBoundary) |
||||
dicer.setBoundary(v.setBoundary); |
||||
}).on('data', function(data) { |
||||
// make a copy because we are using readSync which re-uses a buffer ...
|
||||
var copy = Buffer.allocUnsafe(data.length); |
||||
data.copy(copy); |
||||
data = copy; |
||||
if (!preamble.body) |
||||
preamble.body = [ data ]; |
||||
else |
||||
preamble.body.push(data); |
||||
preamble.bodylen += data.length; |
||||
}).on('error', function(err) { |
||||
preamble.error = err; |
||||
}).on('end', function() { |
||||
if (preamble.body) |
||||
preamble.body = Buffer.concat(preamble.body, preamble.bodylen); |
||||
if (preamble.body || preamble.header) |
||||
state.preamble = preamble; |
||||
}); |
||||
}); |
||||
dicer.on('part', function(p) { |
||||
var part = { |
||||
body: undefined, |
||||
bodylen: 0, |
||||
error: undefined, |
||||
header: undefined |
||||
}; |
||||
|
||||
p.on('header', function(h) { |
||||
part.header = h; |
||||
}).on('data', function(data) { |
||||
if (!part.body) |
||||
part.body = [ data ]; |
||||
else |
||||
part.body.push(data); |
||||
part.bodylen += data.length; |
||||
}).on('error', function(err) { |
||||
part.error = err; |
||||
++partErrors; |
||||
}).on('end', function() { |
||||
if (part.body) |
||||
part.body = Buffer.concat(part.body, part.bodylen); |
||||
state.parts.push(part); |
||||
}); |
||||
}).on('error', function(err) { |
||||
error = err; |
||||
}).on('finish', function() { |
||||
assert(finishes++ === 0, makeMsg(v.what, 'finish emitted multiple times')); |
||||
|
||||
if (v.dicerError) |
||||
assert(error !== undefined, makeMsg(v.what, 'Expected error')); |
||||
else |
||||
assert(error === undefined, makeMsg(v.what, 'Unexpected error: ' + error)); |
||||
|
||||
var preamble; |
||||
if (fs.existsSync(fixtureBase + '/preamble')) { |
||||
var prebody = fs.readFileSync(fixtureBase + '/preamble'); |
||||
if (prebody.length) { |
||||
preamble = { |
||||
body: prebody, |
||||
bodylen: prebody.length, |
||||
error: undefined, |
||||
header: undefined |
||||
}; |
||||
} |
||||
} |
||||
if (fs.existsSync(fixtureBase + '/preamble.header')) { |
||||
var prehead = JSON.parse(fs.readFileSync(fixtureBase |
||||
+ '/preamble.header', 'binary')); |
||||
if (!preamble) { |
||||
preamble = { |
||||
body: undefined, |
||||
bodylen: 0, |
||||
error: undefined, |
||||
header: prehead |
||||
}; |
||||
} else |
||||
preamble.header = prehead; |
||||
} |
||||
if (fs.existsSync(fixtureBase + '/preamble.error')) { |
||||
var err = new Error(fs.readFileSync(fixtureBase |
||||
+ '/preamble.error', 'binary')); |
||||
if (!preamble) { |
||||
preamble = { |
||||
body: undefined, |
||||
bodylen: 0, |
||||
error: err, |
||||
header: undefined |
||||
}; |
||||
} else |
||||
preamble.error = err; |
||||
} |
||||
|
||||
assert.deepEqual(state.preamble, |
||||
preamble, |
||||
makeMsg(v.what, |
||||
'Preamble mismatch:\nActual:' |
||||
+ inspect(state.preamble) |
||||
+ '\nExpected: ' |
||||
+ inspect(preamble))); |
||||
|
||||
assert.equal(state.parts.length, |
||||
v.nparts, |
||||
makeMsg(v.what, |
||||
'Part count mismatch:\nActual: ' |
||||
+ state.parts.length |
||||
+ '\nExpected: ' |
||||
+ v.nparts)); |
||||
|
||||
if (!v.npartErrors) |
||||
v.npartErrors = 0; |
||||
assert.equal(partErrors, |
||||
v.npartErrors, |
||||
makeMsg(v.what, |
||||
'Part errors mismatch:\nActual: ' |
||||
+ partErrors |
||||
+ '\nExpected: ' |
||||
+ v.npartErrors)); |
||||
|
||||
for (var i = 0, header, body; i < v.nparts; ++i) { |
||||
if (fs.existsSync(fixtureBase + '/part' + (i+1))) { |
||||
body = fs.readFileSync(fixtureBase + '/part' + (i+1)); |
||||
if (body.length === 0) |
||||
body = undefined; |
||||
} else |
||||
body = undefined; |
||||
assert.deepEqual(state.parts[i].body, |
||||
body, |
||||
makeMsg(v.what, |
||||
'Part #' + (i+1) + ' body mismatch')); |
||||
if (fs.existsSync(fixtureBase + '/part' + (i+1) + '.header')) { |
||||
header = fs.readFileSync(fixtureBase |
||||
+ '/part' + (i+1) + '.header', 'binary'); |
||||
header = JSON.parse(header); |
||||
} else |
||||
header = undefined; |
||||
assert.deepEqual(state.parts[i].header, |
||||
header, |
||||
makeMsg(v.what, |
||||
'Part #' + (i+1) |
||||
+ ' parsed header mismatch:\nActual: ' |
||||
+ inspect(state.parts[i].header) |
||||
+ '\nExpected: ' |
||||
+ inspect(header))); |
||||
} |
||||
++t; |
||||
next(); |
||||
}); |
||||
|
||||
fs.createReadStream(fixtureBase + '/original').pipe(dicer); |
||||
} |
||||
next(); |
||||
|
||||
function makeMsg(what, msg) { |
||||
return '[' + group + what + ']: ' + msg; |
||||
} |
||||
|
||||
process.on('exit', function() { |
||||
assert(t === tests.length, |
||||
makeMsg('_exit', 'Only ran ' + t + '/' + tests.length + ' tests')); |
||||
}); |
@ -0,0 +1,4 @@ |
||||
require('fs').readdirSync(__dirname).forEach(function(f) { |
||||
if (f.substr(0, 5) === 'test-') |
||||
require('./' + f); |
||||
}); |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue