You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

641 lines
16 KiB

  1. package s3test
  2. import (
  3. "bytes"
  4. "crypto/md5"
  5. "encoding/base64"
  6. "encoding/hex"
  7. "encoding/xml"
  8. "fmt"
  9. "io"
  10. "io/ioutil"
  11. "log"
  12. "net"
  13. "net/http"
  14. "net/url"
  15. "regexp"
  16. "sort"
  17. "strconv"
  18. "strings"
  19. "sync"
  20. "time"
  21. "github.com/goamz/goamz/s3"
  22. )
  23. const debug = false
  24. type s3Error struct {
  25. statusCode int
  26. XMLName struct{} `xml:"Error"`
  27. Code string
  28. Message string
  29. BucketName string
  30. RequestId string
  31. HostId string
  32. }
  33. type action struct {
  34. srv *Server
  35. w http.ResponseWriter
  36. req *http.Request
  37. reqId string
  38. }
  39. // Config controls the internal behaviour of the Server. A nil config is the default
  40. // and behaves as if all configurations assume their default behaviour. Once passed
  41. // to NewServer, the configuration must not be modified.
  42. type Config struct {
  43. // Send409Conflict controls how the Server will respond to calls to PUT on a
  44. // previously existing bucket. The default is false, and corresponds to the
  45. // us-east-1 s3 enpoint. Setting this value to true emulates the behaviour of
  46. // all other regions.
  47. // http://docs.amazonwebservices.com/AmazonS3/latest/API/ErrorResponses.html
  48. Send409Conflict bool
  49. // Set the host string on which to serve s3test server.
  50. Host string
  51. }
  52. func (c *Config) send409Conflict() bool {
  53. if c != nil {
  54. return c.Send409Conflict
  55. }
  56. return false
  57. }
  58. // Server is a fake S3 server for testing purposes.
  59. // All of the data for the server is kept in memory.
  60. type Server struct {
  61. url string
  62. reqId int
  63. listener net.Listener
  64. mu sync.Mutex
  65. buckets map[string]*bucket
  66. config *Config
  67. }
  68. type bucket struct {
  69. name string
  70. acl s3.ACL
  71. ctime time.Time
  72. objects map[string]*object
  73. }
  74. type object struct {
  75. name string
  76. mtime time.Time
  77. meta http.Header // metadata to return with requests.
  78. checksum []byte // also held as Content-MD5 in meta.
  79. data []byte
  80. }
  81. // A resource encapsulates the subject of an HTTP request.
  82. // The resource referred to may or may not exist
  83. // when the request is made.
  84. type resource interface {
  85. put(a *action) interface{}
  86. get(a *action) interface{}
  87. post(a *action) interface{}
  88. delete(a *action) interface{}
  89. }
  90. func NewServer(config *Config) (*Server, error) {
  91. if config.Host == "" {
  92. config.Host = "localhost:0"
  93. }
  94. l, err := net.Listen("tcp", config.Host)
  95. if err != nil {
  96. return nil, fmt.Errorf("cannot listen on localhost: %v", err)
  97. }
  98. srv := &Server{
  99. listener: l,
  100. url: "http://" + l.Addr().String(),
  101. buckets: make(map[string]*bucket),
  102. config: config,
  103. }
  104. go http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
  105. srv.serveHTTP(w, req)
  106. }))
  107. return srv, nil
  108. }
  109. // Quit closes down the server.
  110. func (srv *Server) Quit() {
  111. srv.listener.Close()
  112. }
  113. // URL returns a URL for the server.
  114. func (srv *Server) URL() string {
  115. return srv.url
  116. }
  117. func fatalf(code int, codeStr string, errf string, a ...interface{}) {
  118. panic(&s3Error{
  119. statusCode: code,
  120. Code: codeStr,
  121. Message: fmt.Sprintf(errf, a...),
  122. })
  123. }
  124. // serveHTTP serves the S3 protocol.
  125. func (srv *Server) serveHTTP(w http.ResponseWriter, req *http.Request) {
  126. // ignore error from ParseForm as it's usually spurious.
  127. req.ParseForm()
  128. srv.mu.Lock()
  129. defer srv.mu.Unlock()
  130. if debug {
  131. log.Printf("s3test %q %q", req.Method, req.URL)
  132. }
  133. a := &action{
  134. srv: srv,
  135. w: w,
  136. req: req,
  137. reqId: fmt.Sprintf("%09X", srv.reqId),
  138. }
  139. srv.reqId++
  140. var r resource
  141. defer func() {
  142. switch err := recover().(type) {
  143. case *s3Error:
  144. switch r := r.(type) {
  145. case objectResource:
  146. err.BucketName = r.bucket.name
  147. case bucketResource:
  148. err.BucketName = r.name
  149. }
  150. err.RequestId = a.reqId
  151. // TODO HostId
  152. w.Header().Set("Content-Type", `xml version="1.0" encoding="UTF-8"`)
  153. w.WriteHeader(err.statusCode)
  154. xmlMarshal(w, err)
  155. case nil:
  156. default:
  157. panic(err)
  158. }
  159. }()
  160. r = srv.resourceForURL(req.URL)
  161. var resp interface{}
  162. switch req.Method {
  163. case "PUT":
  164. resp = r.put(a)
  165. case "GET", "HEAD":
  166. resp = r.get(a)
  167. case "DELETE":
  168. resp = r.delete(a)
  169. case "POST":
  170. resp = r.post(a)
  171. default:
  172. fatalf(400, "MethodNotAllowed", "unknown http request method %q", req.Method)
  173. }
  174. if resp != nil && req.Method != "HEAD" {
  175. xmlMarshal(w, resp)
  176. }
  177. }
  178. // xmlMarshal is the same as xml.Marshal except that
  179. // it panics on error. The marshalling should not fail,
  180. // but we want to know if it does.
  181. func xmlMarshal(w io.Writer, x interface{}) {
  182. if err := xml.NewEncoder(w).Encode(x); err != nil {
  183. panic(fmt.Errorf("error marshalling %#v: %v", x, err))
  184. }
  185. }
  186. // In a fully implemented test server, each of these would have
  187. // its own resource type.
  188. var unimplementedBucketResourceNames = map[string]bool{
  189. "acl": true,
  190. "lifecycle": true,
  191. "policy": true,
  192. "location": true,
  193. "logging": true,
  194. "notification": true,
  195. "versions": true,
  196. "requestPayment": true,
  197. "versioning": true,
  198. "website": true,
  199. "uploads": true,
  200. }
  201. var unimplementedObjectResourceNames = map[string]bool{
  202. "uploadId": true,
  203. "acl": true,
  204. "torrent": true,
  205. "uploads": true,
  206. }
  207. var pathRegexp = regexp.MustCompile("/(([^/]+)(/(.*))?)?")
  208. // resourceForURL returns a resource object for the given URL.
  209. func (srv *Server) resourceForURL(u *url.URL) (r resource) {
  210. m := pathRegexp.FindStringSubmatch(u.Path)
  211. if m == nil {
  212. fatalf(404, "InvalidURI", "Couldn't parse the specified URI")
  213. }
  214. bucketName := m[2]
  215. objectName := m[4]
  216. if bucketName == "" {
  217. return nullResource{} // root
  218. }
  219. b := bucketResource{
  220. name: bucketName,
  221. bucket: srv.buckets[bucketName],
  222. }
  223. q := u.Query()
  224. if objectName == "" {
  225. for name := range q {
  226. if unimplementedBucketResourceNames[name] {
  227. return nullResource{}
  228. }
  229. }
  230. return b
  231. }
  232. if b.bucket == nil {
  233. fatalf(404, "NoSuchBucket", "The specified bucket does not exist")
  234. }
  235. objr := objectResource{
  236. name: objectName,
  237. version: q.Get("versionId"),
  238. bucket: b.bucket,
  239. }
  240. for name := range q {
  241. if unimplementedObjectResourceNames[name] {
  242. return nullResource{}
  243. }
  244. }
  245. if obj := objr.bucket.objects[objr.name]; obj != nil {
  246. objr.object = obj
  247. }
  248. return objr
  249. }
  250. // nullResource has error stubs for all resource methods.
  251. type nullResource struct{}
  252. func notAllowed() interface{} {
  253. fatalf(400, "MethodNotAllowed", "The specified method is not allowed against this resource")
  254. return nil
  255. }
  256. func (nullResource) put(a *action) interface{} { return notAllowed() }
  257. func (nullResource) get(a *action) interface{} { return notAllowed() }
  258. func (nullResource) post(a *action) interface{} { return notAllowed() }
  259. func (nullResource) delete(a *action) interface{} { return notAllowed() }
  260. const timeFormat = "2006-01-02T15:04:05.000Z07:00"
  261. type bucketResource struct {
  262. name string
  263. bucket *bucket // non-nil if the bucket already exists.
  264. }
  265. // GET on a bucket lists the objects in the bucket.
  266. // http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGET.html
  267. func (r bucketResource) get(a *action) interface{} {
  268. if r.bucket == nil {
  269. fatalf(404, "NoSuchBucket", "The specified bucket does not exist")
  270. }
  271. delimiter := a.req.Form.Get("delimiter")
  272. marker := a.req.Form.Get("marker")
  273. maxKeys := -1
  274. if s := a.req.Form.Get("max-keys"); s != "" {
  275. i, err := strconv.Atoi(s)
  276. if err != nil || i < 0 {
  277. fatalf(400, "invalid value for max-keys: %q", s)
  278. }
  279. maxKeys = i
  280. }
  281. prefix := a.req.Form.Get("prefix")
  282. a.w.Header().Set("Content-Type", "application/xml")
  283. if a.req.Method == "HEAD" {
  284. return nil
  285. }
  286. var objs orderedObjects
  287. // first get all matching objects and arrange them in alphabetical order.
  288. for name, obj := range r.bucket.objects {
  289. if strings.HasPrefix(name, prefix) {
  290. objs = append(objs, obj)
  291. }
  292. }
  293. sort.Sort(objs)
  294. if maxKeys <= 0 {
  295. maxKeys = 1000
  296. }
  297. resp := &s3.ListResp{
  298. Name: r.bucket.name,
  299. Prefix: prefix,
  300. Delimiter: delimiter,
  301. Marker: marker,
  302. MaxKeys: maxKeys,
  303. }
  304. var prefixes []string
  305. for _, obj := range objs {
  306. if !strings.HasPrefix(obj.name, prefix) {
  307. continue
  308. }
  309. name := obj.name
  310. isPrefix := false
  311. if delimiter != "" {
  312. if i := strings.Index(obj.name[len(prefix):], delimiter); i >= 0 {
  313. name = obj.name[:len(prefix)+i+len(delimiter)]
  314. if prefixes != nil && prefixes[len(prefixes)-1] == name {
  315. continue
  316. }
  317. isPrefix = true
  318. }
  319. }
  320. if name <= marker {
  321. continue
  322. }
  323. if len(resp.Contents)+len(prefixes) >= maxKeys {
  324. resp.IsTruncated = true
  325. break
  326. }
  327. if isPrefix {
  328. prefixes = append(prefixes, name)
  329. } else {
  330. // Contents contains only keys not found in CommonPrefixes
  331. resp.Contents = append(resp.Contents, obj.s3Key())
  332. }
  333. }
  334. resp.CommonPrefixes = prefixes
  335. return resp
  336. }
  337. // orderedObjects holds a slice of objects that can be sorted
  338. // by name.
  339. type orderedObjects []*object
  340. func (s orderedObjects) Len() int {
  341. return len(s)
  342. }
  343. func (s orderedObjects) Swap(i, j int) {
  344. s[i], s[j] = s[j], s[i]
  345. }
  346. func (s orderedObjects) Less(i, j int) bool {
  347. return s[i].name < s[j].name
  348. }
  349. func (obj *object) s3Key() s3.Key {
  350. return s3.Key{
  351. Key: obj.name,
  352. LastModified: obj.mtime.Format(timeFormat),
  353. Size: int64(len(obj.data)),
  354. ETag: fmt.Sprintf(`"%x"`, obj.checksum),
  355. // TODO StorageClass
  356. // TODO Owner
  357. }
  358. }
  359. // DELETE on a bucket deletes the bucket if it's not empty.
  360. func (r bucketResource) delete(a *action) interface{} {
  361. b := r.bucket
  362. if b == nil {
  363. fatalf(404, "NoSuchBucket", "The specified bucket does not exist")
  364. }
  365. if len(b.objects) > 0 {
  366. fatalf(400, "BucketNotEmpty", "The bucket you tried to delete is not empty")
  367. }
  368. delete(a.srv.buckets, b.name)
  369. return nil
  370. }
  371. // PUT on a bucket creates the bucket.
  372. // http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketPUT.html
  373. func (r bucketResource) put(a *action) interface{} {
  374. var created bool
  375. if r.bucket == nil {
  376. if !validBucketName(r.name) {
  377. fatalf(400, "InvalidBucketName", "The specified bucket is not valid")
  378. }
  379. if loc := locationConstraint(a); loc == "" {
  380. fatalf(400, "InvalidRequets", "The unspecified location constraint is incompatible for the region specific endpoint this request was sent to.")
  381. }
  382. // TODO validate acl
  383. r.bucket = &bucket{
  384. name: r.name,
  385. // TODO default acl
  386. objects: make(map[string]*object),
  387. }
  388. a.srv.buckets[r.name] = r.bucket
  389. created = true
  390. }
  391. if !created && a.srv.config.send409Conflict() {
  392. fatalf(409, "BucketAlreadyOwnedByYou", "Your previous request to create the named bucket succeeded and you already own it.")
  393. }
  394. r.bucket.acl = s3.ACL(a.req.Header.Get("x-amz-acl"))
  395. return nil
  396. }
  397. func (bucketResource) post(a *action) interface{} {
  398. fatalf(400, "Method", "bucket POST method not available")
  399. return nil
  400. }
  401. // validBucketName returns whether name is a valid bucket name.
  402. // Here are the rules, from:
  403. // http://docs.amazonwebservices.com/AmazonS3/2006-03-01/dev/BucketRestrictions.html
  404. //
  405. // Can contain lowercase letters, numbers, periods (.), underscores (_),
  406. // and dashes (-). You can use uppercase letters for buckets only in the
  407. // US Standard region.
  408. //
  409. // Must start with a number or letter
  410. //
  411. // Must be between 3 and 255 characters long
  412. //
  413. // There's one extra rule (Must not be formatted as an IP address (e.g., 192.168.5.4)
  414. // but the real S3 server does not seem to check that rule, so we will not
  415. // check it either.
  416. //
  417. func validBucketName(name string) bool {
  418. if len(name) < 3 || len(name) > 255 {
  419. return false
  420. }
  421. r := name[0]
  422. if !(r >= '0' && r <= '9' || r >= 'a' && r <= 'z') {
  423. return false
  424. }
  425. for _, r := range name {
  426. switch {
  427. case r >= '0' && r <= '9':
  428. case r >= 'a' && r <= 'z':
  429. case r == '_' || r == '-':
  430. case r == '.':
  431. default:
  432. return false
  433. }
  434. }
  435. return true
  436. }
  437. var responseParams = map[string]bool{
  438. "content-type": true,
  439. "content-language": true,
  440. "expires": true,
  441. "cache-control": true,
  442. "content-disposition": true,
  443. "content-encoding": true,
  444. }
  445. type objectResource struct {
  446. name string
  447. version string
  448. bucket *bucket // always non-nil.
  449. object *object // may be nil.
  450. }
  451. // GET on an object gets the contents of the object.
  452. // http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectGET.html
  453. func (objr objectResource) get(a *action) interface{} {
  454. obj := objr.object
  455. if obj == nil {
  456. fatalf(404, "NoSuchKey", "The specified key does not exist.")
  457. }
  458. h := a.w.Header()
  459. // add metadata
  460. for name, d := range obj.meta {
  461. h[name] = d
  462. }
  463. // override header values in response to request parameters.
  464. for name, vals := range a.req.Form {
  465. if strings.HasPrefix(name, "response-") {
  466. name = name[len("response-"):]
  467. if !responseParams[name] {
  468. continue
  469. }
  470. h.Set(name, vals[0])
  471. }
  472. }
  473. if r := a.req.Header.Get("Range"); r != "" {
  474. fatalf(400, "NotImplemented", "range unimplemented")
  475. }
  476. // TODO Last-Modified-Since
  477. // TODO If-Modified-Since
  478. // TODO If-Unmodified-Since
  479. // TODO If-Match
  480. // TODO If-None-Match
  481. // TODO Connection: close ??
  482. // TODO x-amz-request-id
  483. h.Set("Content-Length", fmt.Sprint(len(obj.data)))
  484. h.Set("ETag", hex.EncodeToString(obj.checksum))
  485. h.Set("Last-Modified", obj.mtime.Format(time.RFC1123))
  486. if a.req.Method == "HEAD" {
  487. return nil
  488. }
  489. // TODO avoid holding the lock when writing data.
  490. _, err := a.w.Write(obj.data)
  491. if err != nil {
  492. // we can't do much except just log the fact.
  493. log.Printf("error writing data: %v", err)
  494. }
  495. return nil
  496. }
  497. var metaHeaders = map[string]bool{
  498. "Content-MD5": true,
  499. "x-amz-acl": true,
  500. "Content-Type": true,
  501. "Content-Encoding": true,
  502. "Content-Disposition": true,
  503. }
  504. // PUT on an object creates the object.
  505. func (objr objectResource) put(a *action) interface{} {
  506. // TODO Cache-Control header
  507. // TODO Expires header
  508. // TODO x-amz-server-side-encryption
  509. // TODO x-amz-storage-class
  510. // TODO is this correct, or should we erase all previous metadata?
  511. obj := objr.object
  512. if obj == nil {
  513. obj = &object{
  514. name: objr.name,
  515. meta: make(http.Header),
  516. }
  517. }
  518. var expectHash []byte
  519. if c := a.req.Header.Get("Content-MD5"); c != "" {
  520. var err error
  521. expectHash, err = base64.StdEncoding.DecodeString(c)
  522. if err != nil || len(expectHash) != md5.Size {
  523. fatalf(400, "InvalidDigest", "The Content-MD5 you specified was invalid")
  524. }
  525. }
  526. sum := md5.New()
  527. // TODO avoid holding lock while reading data.
  528. data, err := ioutil.ReadAll(io.TeeReader(a.req.Body, sum))
  529. if err != nil {
  530. fatalf(400, "TODO", "read error")
  531. }
  532. gotHash := sum.Sum(nil)
  533. if expectHash != nil && bytes.Compare(gotHash, expectHash) != 0 {
  534. fatalf(400, "BadDigest", "The Content-MD5 you specified did not match what we received")
  535. }
  536. if a.req.ContentLength >= 0 && int64(len(data)) != a.req.ContentLength {
  537. fatalf(400, "IncompleteBody", "You did not provide the number of bytes specified by the Content-Length HTTP header")
  538. }
  539. // PUT request has been successful - save data and metadata
  540. for key, values := range a.req.Header {
  541. key = http.CanonicalHeaderKey(key)
  542. if metaHeaders[key] || strings.HasPrefix(key, "X-Amz-Meta-") {
  543. obj.meta[key] = values
  544. }
  545. }
  546. obj.data = data
  547. obj.checksum = gotHash
  548. obj.mtime = time.Now()
  549. objr.bucket.objects[objr.name] = obj
  550. h := a.w.Header()
  551. h.Set("ETag", fmt.Sprintf(`"%s"`, hex.EncodeToString(obj.checksum)))
  552. return nil
  553. }
  554. func (objr objectResource) delete(a *action) interface{} {
  555. delete(objr.bucket.objects, objr.name)
  556. return nil
  557. }
  558. func (objr objectResource) post(a *action) interface{} {
  559. fatalf(400, "MethodNotAllowed", "The specified method is not allowed against this resource")
  560. return nil
  561. }
  562. type CreateBucketConfiguration struct {
  563. LocationConstraint string
  564. }
  565. // locationConstraint parses the <CreateBucketConfiguration /> request body (if present).
  566. // If there is no body, an empty string will be returned.
  567. func locationConstraint(a *action) string {
  568. var body bytes.Buffer
  569. if _, err := io.Copy(&body, a.req.Body); err != nil {
  570. fatalf(400, "InvalidRequest", err.Error())
  571. }
  572. if body.Len() == 0 {
  573. return ""
  574. }
  575. var loc CreateBucketConfiguration
  576. if err := xml.NewDecoder(&body).Decode(&loc); err != nil {
  577. fatalf(400, "InvalidRequest", err.Error())
  578. }
  579. return loc.LocationConstraint
  580. }